世間でGraphQLが流行っているから採用検討しているものの、いろいろ調べたけど正直実感が湧かない…という方もいますよね。 そこでフロントエンドとバックエンドの両方の観点から、いくつかの典型的な疑問とその回答をご用意しました。
これは私が質問された時に答えている内容をまとめたものですが、何かの参考になれば幸いです。
既にGraphQLについて何となく知っている、例えば「QueryとMutationなるものがあるらしい」ぐらいは理解していると想定して書いています。 何が書いてあるか分からない項目は読み飛ばしてください。
はじめに編
GraphQLの素晴らしいところは何ですか
最も素晴らしいのは、GraphQLクライアントによりフロントエンドのDX(開発時の体験)が圧倒的に良くなる点です。
フロントエンド編
フロントエンドにどんなメリットがありますか
GraphQLクライアントであるApolloやRelayが使えることがメリットだと思います。 どのGraphQLクライアントを使用するかによって多少変わりますが、基本的にはデータの取得や更新においてバグりやすい処理が減ります。 DOMはjQueryからReactなどに移行することで大幅に宣言的に書けるようになりましたが、同じ現象がデータの取得や更新でも起こります。宣言的なデータ管理といったところです。どこまで宣言的に書けるかはGraphQLクライアントによります。 また、データに自動的に型が付くので開発しやすいと思います。
基本的にはコードも減ってバグも減るので、生産性は上がると思います。
Reduxなどの状態管理のソフトウェアを使う必要がありますか
必要ありません。ApolloやRelayなどのデータ管理を行うGraphQLクライアントを使う場合は同様の処理をやってくれます。 API経由以外のローカルステートもApolloとRelayともに管理できます。
とは言え、独自の観点からReduxと組み合わせて使っている人もいますので、組み合わせられないわけではないです。
ApolloとRelayどちらが良いですか
一長一短です。 すごく単純化すると、あなたがReactを使っていて、TypeScriptをstrictで使うような機械に極力任せたい(そのために努力できる)派閥の人ならRelayが良いかもしれません。 そうでないならApollo一択。
他に有名なGraphQLクライアントはありますか
Apollo、Relayに次いで有名なのはurqlです。説明できるほど詳しくありませんが、ApolloやRelayよりシンプルに使えると思います。 Apolloのハマりやすさ、Relayの厳しすぎる要件が嫌な人はurqlを使っている印象です。
TypeScriptと相性は良いですか
はい。型がある点がGraphQLメリットの1つなので、間違いなく便利に使えます。 他に型のあるAltJS(例えばReasonML)とも相性は悪くありません。そちらの方が最高と言ってる人もいるくらいですが、入門者にとっては情報量や品質の観点からTypeScript以外を試すのは現実的ではないと思います。
一回で色々なオブジェクトを取得するのでUIの表示が遅くなりませんか
たくさんの情報をクエリで一気に要求しているとAPIのレスポンスは当然遅くなります。バックエンドに魔法の技術はありません。 困ったら次のことを検討してください。
- UIとしてスケルトンを表示してください
- 可能な限りGraphQLクライアントのキャッシュを活用してください
- クエリを分割してください。可能な限りネストを浅くしてください
- そのうち@deferが使えるようになると思うので検討してください
現実問題として、バックエンドでのSQLのチューニングはRESTよりやり辛くなってます。パフォーマンスによるユーザー体験の影響はREST時代よりもクライアントの責任が増えていると理解してください。
バックエンド編
バックエンドにどんなメリットがありますか
エンドポイントを増やさなくて良い、マイクロサービスでBFFを組みやすい、などが代表例だと思います。 ただ、これは個人的な意見ですが、APIの要件はフロントエンドの都合に合わせる時代だからGraphQLを選択している程度にしか考えていません。 もちろん幸せになったバックエンドエンジニアの話も調べたら出てくると思うので、そちらをご覧いただければと思います。
APIに型をつけるという観点で、GraphQLとXXX(好きなソフトウェア)と違いはありますか
たぶんないです。RESTのAPIに型をつけるソフトウェアを使って、GraphQLの型付けの性質と同じようなものを作ることは可能です。 これは型付けだけでなく、あらゆるGraphQLの特徴は論理的には別の技術で個別に代替できると思います。 GraphQLの良いところは、いくつかの特徴が組み合わせで固定されていて、エコシステムがその特徴を前提としたツールを構築できる点にあると思います。 FacebookのオレオレAPI仕様に名前を付けて標準化したことで、みんなが乗っかれたことが良かったと思ってます。
スキーマファーストとコードファーストのどちらが良いですか
コードファーストだと思いますが、断言できません。 GraphQLの世界で強い影響力を持つApolloはスキーマファーストを主導してきました。一方でApolloと利害関係のないフレームワークでは近年コードファーストをメインに採用する傾向が強まっています。正直、今が一番どうなるのか分からない時期です。
私はコードファースト派です。実運用でスキーマだけ先に書いてもメリットを得るのは難しい、バックエンドでスキーマから生成した型を使ってモデルを書くのは最悪のDXだ、などと思ってますが、たくさんの論争があるのでここで言い切るのは難しいです。
CDNでキャッシュが効かないと聞きましたが本当ですか
はい。これも多くの論争がありますが、要約すると「諦めて他のことをした方が生産的」になります。
まず、ブログのような大部分が静的なページを運営している場合はCDNが変わらず必要だと思いますが、それはそれで(APIのレスポンスではなく)サーバーサイドでレンダリングしたページをCDNのキャッシュにのせてもらって、GraphQLとは関係なくよしなにして下さい。 以下はそれ以外のケースについて話します。
この問題はGraphQLではPOSTメソッドでやり取りを行うので、(GETを対象にした一般的な)CDNではキャッシュが効かせにくいという話なのですが、一応仕様的にはGraphQLはGETを使用できます。また、GraphQL対応のCDNも存在しています。
ただ、いくつかの点で考えると、やはりCDNでキャッシュを効かせられるように頑張る労力にリターンが見合うかはちょっと…。
- GraphQLではクライアントでキャッシュを効かせる世界観なので必要性は薄め
- RESTfulのようにリソースとリクエストが対応するわけではないので、仮に同じオブジェクトを取得していても様々なリクエスト(query)の形があってキャッシュが効きにくい
- CDNでキャッシュするとクライアントと2重でキャッシュのレイヤが生まれるため、複雑怪奇なバグに見舞われる可能性がある
もちろん、画像や動画は別のエンドポイントで引き続きCDNのキャッシュを使用してください。 APIのレスポンスを高速にしたい場合はCDNではなくサーバーに到達して以降の何かのキャッシュを頑張った方が吉。
N+1は発生しますか
何も考えないと発生します。 GraphQLではコンテキスト(queryの形)によって関連するテーブルを読み込んだり読み込まなかったりするので、我々が事前にJOINしたSQLを組み立てるのは容易ではありません。 RESTの決まったレスポンスのために手動でゴリゴリにチューニングしたSQLより速くなることはないと理解しつつ、dataloaderのような仕組みを使ってください。
1発であらゆるオブジェクトが取得できるって負荷やばくないですか
やばいので、複雑度と深さで計算してクエリを制限します。 GraphQLは1発であらゆるオブジェクトを取得するためのものと勘違いされてる節がありますが、1発でアプリケーションに必要な全てのデータを取得するのはさすがに非現実的です。 適切にクエリは分けてください。まずはページ単位で分ける感じで。
エラーが発生したのに200を返すのはおかしいと思います
GraphQLではレスポンスコードは常に200で、一般的にはエラーはレスポンスボディ(のJSON)のerrors
に入れて返します。
まずこの仕組みの利点から説明すると、利点はエラーが一部発生しても一部のデータは返すことができる点です。 GraphQLでは1回のリクエストで多くのデータを取得できますが、一部でエラーが発生しても返ってきているデータがあるならそれをレンダリングできます。 例えばBFFでまとめているマイクロサービスで一部がダウンしている状況でも、動いている部分だけ返すことができますし、逆にこれができないとクエリは丸ごと失敗するのでフロントエンドから見ると全体がダウンしているように見えてしまいます。
とは言え、個人的にはサーバーサイドの実装を手抜きしてMutationのバリデーションエラー以外は普通に401とか500で全体を失敗させて返すこともありますし、それでハンドリングできます。
どうやって認証しますか
特にRESTと変わりません(異なる方法でも実装できますが)。
ヘッダのAuthorization
に認証情報をのせて、それをいつも通りに処理してください。
RESTのAPIと共存できますか
はい。色々やり方があると思います。
BFFでAPIをまとめてGraphQLだけにすることもできますし、GraphQLとRESTの両方のAPIを持つサーバーにクライアントからアクセスすることもできます。 GraphQLクライアントからRESTのAPIが叩けるかは使用しているGraphQLクライアントによりますが、使えないなら普通にRESTのAPIが叩けるライブラリ(axiosとか)を使ってください。
既存の外部システムと連携する部分などは移行が難しいでしょうし、GraphQLだけで作れないこともあるでしょう。 例えば、私はフロントエンドで画像を処理するライブラリを使用していて、その中にサーバーに画像をアップロードする機能があるのですが、GraphQLに対応していないのでその部分のバックエンドは普通にRESTで書いてます。 ただし、当該処理がDBと関係ないので許されるという側面が多大にあり、通常はGraphQLで書けるだけ書くのが無難です。
Relay Server Specificationに準拠する必要がありますか
ケースバイケースです。
Node
インターフェース、Global Object Identification、Connectionベースのページネーションのことを指して、Relay Server Specificationと言います。
これらはフロントエンドでコンポーネント指向のアプリケーション(Reactとか)を書いてある場合は提供した方が良いと個人的には思います。コンポーネントにデータをバインドしやすくなります。Relayを使う場合は必須です。
あとで導入するのはかなりの痛みが伴いますので、最初によく検討してください。
node(id)
フィールドは神APIではないですか
その通りです。あらゆるものと結合している、悪い意味での神API(ゴッドAPI)です。
node(id)
フィールドはユニークなid
であらゆる単一のオブジェクトを取得できるメソッド的なものです。
あらゆるオブジェクトが取得できるので、公開されているあらゆる型(モデル、テーブル)の情報が引けます。なのでバックエンドのコード上ではnode(id)
のリゾルバからは様々な依存関係ができます。幸いなことに単方向の依存関係ですが。
RESTではありえない考え方ですが、ここにREST的な意味での正しい答えはありません。まごうことなき神APIが目の前にあるだけです。
Connectionベースのページネーションを調べましたが意味が分かりませんでした
たぶんConnectionとEdgeという謎のオブジェクトがページネーションを複雑化させてるように見えるんですよね。分かります。こちらをご覧ください。