aspida
aspida copied to clipboard
What do you think about polymoph? polymorphの利用について教えて下さい。
polymorph は入力に関連する型に応じて出力の型を変えられるという認識です。
https://github.com/aspida/aspida/issues/689 が関連issueです。
polymorph を今後も様々な変更とともにサポートし続けると、複雑なif文を生成するような必要性が感じられます。
Id | Id[] => User | User[] という型で処理しようとすると使用者が分岐が必要で大変だ、というような課題を解決したいということだと思います。
これに対し、そもそもそのようなAPIを新規でつくるのは避けられるべき、ということで、既存で存在するなら分岐で頑張ってもらいながら、次のメジャーバージョンで外すなどの対処をしたほうがいいのではないかという話があります。
if文が今後増えてしまうだろう要因の一つは、Blobもしくはstringを返す、といったようなパターンを適切に対処しようとした際に大変なこと。
シンプルさを優先したほうがいいのではないか。など。
もし現在polymorphを積極的に使っている方がいればぜひご意見頂きたいです。それ以外でも何かあれば気軽に教えていただければ助かります。
このIssueきっかけで導入しました https://github.com/aspida/aspida/issues/554 (今気づいたけど追加でコメント来てるな)
V2では外して aspida としてはエンドポイントをパス変数の別名にしてもらおうと考えてた frourioは非対応だけど、これはバックエンドも自分で握れる状況ならPolymorphが必要なAPI定義をすべきではないという思想 FEはAPI仕様を変えられないのでaspidaでは代替案としてパス変数として分離
api/
- hoge/
- index.ts <- polymorph
api/
- _hogeA@string/
- index.ts
- _hogeB@string/
- index.ts
に変えるイメージ 私は実務でPolymorph使わず後者の方法でaspida使っている
パスパラメータでだけpolymorphを使えるわけではないのでその場合はどうなりますかね?
フロントエンドで変えられないのはありますが、
Id | Id[] => User | User[] でしかできないとしても、 Array.isArray() とかで頑張ってもらうことになるかな…とかは思います。
Id[] => User[] しか使わないと決めて、フロントエンド側の定義でこれのみを記述するとか。
実際には本当に維持しにくい仕組みなのか、は今後の機能追加と調整して決定する必要があると思いますが、大変で複雑な割に恩恵が少ないとは思います。
私の「パス変数として分離」パターンはそれも解決しているつもりだったけどこれは意図と違う?
api/_hogeA@string/index.ts
export type Methods = {
get: {
resBody: number
}
}
api/_hogeB@string/index.ts
export type Methods = {
get: {
resBody: string
}
}
sample.ts
const num = await $api._hogeA('hoge').$get() // number
const str = await $api._hogeB('hoge').$get() // string
両方とも GET api/hoge という同一リクエストを生成するという理解であっていますか…?
個のケースですと、サーバー側で違いが現れることはないので number か string かが変わることがないと思いますし、逆にパスパラメータ以外の、bodyのみの変化で返す値の型を決めたい、という場合が polymorph ということかと思います。
例えば、 {format: "number", limit: 10} では number[] で返すが、 {format: "bigint", limit: 10} なら string[] で返す(それを同一エンドポイントで)、みたいな。
ところで、polymorphはやはり暗黙のルールが多いというのも気になりそうです。(polymorphを残し続けることのデメリット)
例えば、 {format: "json"} なら JSON、 {format: "formdata"} なら FormData をそれぞれ返す、みたいなpolymorphic定義は許容されていません。
こういった制約をドキュメントと、ツール(DefineMethods<T>, eslint-plugin)で網羅するのはなかなか大変なことかなと思います。(現状はないですが、やはりどこかであったほうがいいかなと思います)
逆にサポートしてしまうというのも考えられますが、if文を自動生成して、かつバリデータをうまく対応する…というのはやはりこちらも複雑になりすぎる気がします。
すみません、上記書き間違えてました
api/_hogeA@string/index.ts
export type Methods = {
post: {
reqBody: number
resBody: number
}
}
api/_hogeB@string/index.ts
export type Methods = {
post: {
reqBody: string
resBody: string
}
}
両方とも POST api/hoge という同一リクエスト
たとえば、パスパラメータだと以下のように
const num = await $api._hogeA('fuga').$get() // number
const str = await $api._hogeB('fuga').$get() // string
api/fuga へのリクエストも作れると思うのですが、これを禁止したい(パスの変動を許さない)状態で、この書き方ができなくなるのではないかな、と思います。
今はないけどv0には
api/_hogeB@HogeType/index.ts
という型名を指定する方法があった
api/@types.ts
export type HogeType = 'hoge'
これで制約をかけられる 幽霊型好きなのでv2でこの機能を復活させる予定だった
なるほどです。それなら実現自体はできそうです。