GraphQLは銀の弾丸か
銀の弾丸なんてないと言われつつ、割とGraphQLはRESTの上位互換のような、銀の弾丸扱いされることが増えてきていると感じる。
過去GraphQLを触ってきた中で疑問に思うことがあるものの、まとまった技術記事にするほど思考がまとまってないのでメモがわりにここに供養する。
(「過大評価かも?」って内容なので反対意見ばかりになるが、別に反対派ではない)
本当に開発速度が速いのか
割とGraphQLと合わせて言われるのが開発速度が速いという話だ。
これについてはGraphQLだから必ずしも早くなるかは疑問に感じる。 何と比較し、どのようなスキーマ設計で開発を進めていくかに依存しているからだ。
つまり「仮説検証フェーズだから素早く作るためにGraphQLだよね」みたいにGraphQLと開発速度を短絡的に繋げることはできないんじゃないだろうか。
半自動作成できる
まずこの主張の前提には「スキーマ定義がテーブル定義と同等である」が多く含まれていることが多い。 たとえばHasuraのようなサービスもそうだし、テーブル定義をもとに半自動的にスキーマを生産することでなんか素早くできるよねって言ってるケースが多い。
ただ後にも書くが「スキーマ定義がテーブル定義と同等である」には疑問があるし、半自動的に作成されたコードをバックエンドに使うには怖い。 特にそれだけだとほぼ剥き出しのDBをいじるのと同義だ。 Selectで自由にデータを取得でき、CreateやUpdateで自由に値が変更可能なインターフェースで良ければいくらでもやりようがある。
それでいいならFirestoreでも使えばいいし、RESTでも自動作成できる。極論RDBを直で叩くみたいな話になる。
ある意味バックエンド開発の目的はDBを直に触らせずインターフェースを規定するためにしてるのに、GraphQL文脈だと何故かそれが失われがちだ。
複数エンドポイントの統合
ということで価値があるのはクエリの柔軟性の高さにより複数個作らないといけなかったエンドポイントが少なくすむようになることかなと思う。
たとえばフロントエンドから自由にLIMIT、OFFSETが指定できたり、リレーションが自由に引っ張ってこれたりするのが嬉しいという話だ。
ただ柔軟性の高さは怖さでもある。
たとえばリレーションがめちゃくちゃ離れたデータを1クエリで取得するために強引に辿って行って、むしろ不必要なデータを大量にとってきてパフォーマンスを悪化させるような例もある。
N+1が起こるかもしれないし、Indexの効いてないクエリ、膨大なディスクアクセスが発生するかもしれない。
これに対してバックエンド側から防ぐことは難しい。
というかこれをコントロールしようとするとクエリの柔軟性という利点は失われる。
フロントエンドの体験
使ってるライブラリによるかもしれないがフロントエンドの開発体験はむしろ悪くない?って思う。 わざわざクエリ書かないといけないし、gql書いた後にTypeScriptファイルが生成されてそれを型情報として渡して...みたいなステップは(半自動で裏に回るにしても)相対的に手数が多い。 RESTでOpenAPIでクライアント生成してできた関数を叩けば型情報付きで返ってくるのは、フロントエンドに閉じた話であればそっちの方が開発楽じゃない?と思う。
というのでフロントエンドエンジニアがGraphQLが楽!っていうのはあまりわからない。 バックエンドエンジニアとのコミュニケーションなく勝手に欲しいデータ取ってこれるって話なのかな?
Server Components
そもそも最近はNext.jsでgetServersidePropsするなり、RemixでgetLoaderするなり、Server Components内でdb叩くなり、いわゆるRailsのような書き心地でページごとにDB叩く書き方が容易になってきた。 型情報も簡単に受け渡しできるし、パフォーマンス最適化の考慮やセキュリティ的な懸念、モデル設計の苦労もなくなる。 開発速度という観点では一番これが早い。
モデリングの難しさ
やっぱり人間として一番楽なのは目の前の実装に最適化した実装だ。 たとえば「ユーザー一覧ページ」では、そのページで必要な情報だけ返すと言うのが一番議論の余地が少ない実装者ごとによるブレが少ないものになる。
一方でGraphQLは(その利点を享受するためには)ユースケースに左右されないモデリングが必要となる。 たとえばUserはユーザー一覧ページで使われるか、ユーザー詳細ページで使われるか、はたまた商品一覧ページでその商品へのコメントしたユーザー情報を表示するために使われるかに依存しないモデリングが必要となる。 そうなると何をどういう関係性のもとモデリングしていくかのセンスが求められることとなる。
これに対して一番簡単で大きく失敗しづらいのは、先にも書いたがテーブル定義と一致させる方法だ。 しかしテーブル定義はインフラの都合が絡むものであり、いわゆる設計界隈で批判される「データモデルとドメインモデルは別」という問題もある。
さらにここでいうモデルは参照系であることを考えると、ドメインモデルと一致させるのでなくCQRSのように異なるモデルで考えるのが適切にも思う。
GraphQL界隈はここどうしてるんだろう?って毎回疑問に思うが、本も記事も登壇もここについて詳しく言及してるものがほとんど見当たらなかった。
セキュリティのコントロールが難しさ
実体験として、やっぱりセキュリティはガバガバになりやすいなと感じる。 セキュリティ意識が薄いとなんでもできるスキーマ定義にしがちで、「これが欲しくなった!」って要件があると無秩序に機能が増えがちだ。
しかしデータに対する権限設定って結構難しい。 シンプルに「このデータは誰がどういう条件で見えるべきか」ってのを全モデルの全スキーマに対して検討するのはコストがかかる。 ましてや「このデータは本人か管理者だけ見れる」「このデータは再認証後に見れる」など複雑な権限要求されたら尚更だ。
特にリレーションを追っての抜け道もできやすい。 「商品情報のコメントからユーザ情報に辿り着いて、そこからそのユーザの年収が取得できます」みたいなことが起こりうる。 自分が完全にコントロールできるならまだしも、たとえばマネジメントレイヤーとしてメンバーに自律的に開発してもらう中でこのリスク許容するのなかなかしんどくない?って思う。
これを踏まえるとやっぱりテーブルの写像としてのスキーマ構造はあんまり良くないんじゃないかとなり、Viewごとのモデルをしっかり定義する運用が安全なんじゃないかって思っている。 ただそうなるとGraphQLの利点って薄いんだよね。
動作の担保が難しい
これはテストを書いていて思ったが、どこまでテスト書けば良いの?っていう。 各データを返す、返さない、リレーションなどを含めると、ほぼ無限にパターンが考えられる。 もちろんFWが基礎的なところは担保してるよねってことで妥協して良いとは思うが、書いたテストが100%動作を担保できてるっていう安心感は薄いよなと感じる。
BFFとしてのGraphQLはあり
ここまで否定的に書いたが、BFFとしてのGraphQLはありなんじゃないかと思う。 特定のフロントエンドに依存した構造にならずにすむからだ。
特に「getServersideProps内でGraphQL叩く」とかならセキュリティの懸念も薄くなりほぼDBでも問題がなくなる。
つまりはWebのみだったサービスがアプリ展開もすることになり、その差異をRESTだと埋めづらい!というときにBFFでGraphQLサーバを建てるってのがありがちなユースケースかなーと。
少なくとも明確な理由を持って採用すべき技術であり、「なんかいけてるっぽいからGraphQL採用したい!」「なんか開発スピード上がるっぽいからGraphQLだ!」は将来の負債になりかねないと感じる。