バックエンドエンジニアとしてなぜGraphQLに辿り着いたのか
8年前くらいにFiNC(フィンク)という会社で働いてた頃、当時はRailsでマイクロサービスを真剣にやっていました。マイクロサービスが一番盛り上がったのが2018年くらいなので、その3年くらい前から先行投資して頑張っていた会社でした。
クライアントはiOS・Android・Web Frontend で、フロントエンドの種別が多くてWeb APIには課題感を持っていました。 当時はRESTful APIでしたが、メイン課題は特定のページ向けに作られたAPIが多くて再利用性が低かったり、似たAPIを叩くと少し違うデータが返ってきて扱いにくかったですね。
上はhogesというAPIで、 下はfugasの中にhogeがくっついてくるんですが、フィールド名が違ったり、一方にあるフィールドがこっちには無いこともありました。
もう1個は神APIというもので、v3/meというエンドポイントがたくさんの情報を回してきて便利で使うんですけど、フィールドとかを追加しすぎてパフォーマンスがどんどん悪くなるんですね。
上記の2つの課題は再利用性の低さと再利用性の高さで真逆に見えますが、根本的には一緒で、フロントエンドやUIのユースケースにAPIが引っ張られすぎている点が共通の課題としてあります。
フロントエンドやUIにAPIが引っ張られすぎたことで、ユースケースの変化に対応しにくさが課題に
バックエンドエンジニアとしてユースケースの変化に対応しやすいリソース指向のAPI設計にしたいというのは、当時のモチベーションとしてありました。そこで、安定的なリソースを定義して徹底できる技術選定を行いました。 エンドポイントごとではなく、リソースごとにJSONシリアライザを1個1個当てていきます。 そうすると徐々に粒度感の良いAPIが増えて神APIが新しく使われなくなって、改善傾向に行きました。
次の課題としては、ある一覧画面は200件返してフィールドは10個のうち2個しか使ってないから絞りたいとか、逆にこっちの画面ではネストした情報まで欲しい、みたいな柔軟性の部分ですね。
他社のWantedlyさんの2017年ごろの事例で、今みたいな課題に対してfieldsというのをクエリパラメータで渡せて絞れるとか、includeとするとネストした情報まで取ることをやっていたりしていました。
ここまでのまとめとしては、Web APIをリソース指向に寄せていくために 技術選定でなるべくレールを敷いていきたくて、そのうえでリソース指向だけどデータの取り方に柔軟性を持たせたい時がある、という感じです。 1年2年かけてREST APIを改善してきましたが、これってGraphQLで解決するのではないかと。
リソース指向のAPI設計にするには、REST APIは限界。GraphQLの活用へ。
なぜかというとリソース指向に寄せたいからです。 GraphQLもグラフ上で、グラフ上の1個1個のGraphQL Typeはまさにリソースだと思っていて、 逆にユースケースごとにAPIを作るのはGraphQLではかなり難しいんですよ。普通はエンドポイントごとに対してJSONベタ書きできますが、 GraphQLでAPIを作るとGraphQL Typeを絶対に作らないと書けないので、ユースケースに合わせてAPIを作る思考にそもそもなりにくい。 つまり、レールを敷けているわけです。
それ以外にも課題があって、十分なAPIドキュメントが無かったんですよね。 レスポンスの型が定義されてないから、結局何が返ってくるかがチェックしないと分からなくて、クライアントから見て不便でした。
そこで、当時出たてのOpenAPI 3.0でドキュメントを書く形で改善を図りました。 そうすると、せっかくドキュメントを書いても実際の実装と解離してドキュメントの信憑性が薄れることが次の課題になりました。
解決策としては、 OpenAPIのドキュメントを使って実際のrequest/responseをバリデーションしました。 この時は、Rubyのラックミドルレイヤーというレイヤーで解決しようとしてcomitteeというgemを使いましたが、メジャーライブラリではなかったのでOpenAPI 3.0への対応は自分たちで進めました。
こうすると GraphQL SchemaがそのままAPIドキュメントになって、実装との解離がまず起きない。 だから、実装の解離してない新鮮なドキュメントが常にあるのはGraphQLの大きな強みです。
もう1個エコシステム的な課題もあって、Railsの文脈でWebAPIを改善していく時に苦労しました。 先ほどのリソース指向のレールを敷くところで、active_model_serializersというgemを選定しましたが、その後メンテナンスもリリースも止まってリリースされない状態になりました。 さっきのcomitteeも自分たちで対応する必要がありましたが、GraphQLの方がエコシステムはかなり強いです。
ここまでのまとめとして、WebAPIはある程度規模が大きくなると、リソース指向かつ一定の柔軟性を持ったデータができて、実装と乖離しないAPIドキュメントが手に入ることが大事ですが、GraphQLは全て満たしていました。 このような経緯でGraphQLに辿り着きました。
現代においてもバックエンドにGraphQLを使うのは嬉しいのか
現代でよくある大規模サービスのアーキテクチャ構成は、バックエンドが複数あって、それをまとめ上げる必要が出てきます。
よくあるGraphQLの使われ方って、ウェブアプリからGatewayがあって、そこをGraphQLにしておくとフロントエンドがデータを取りやすくて嬉しいという感じです。 僕はバックエンドエンジニアなので、バックエンドはGraphQLで作りたいんですよ。どちらかというとバックエンド間もGraphQLで繋げたいです。
ちょっと別トピックで、React Server Components(RSC)みたいな話が出てきていますが、この辺が出てくるとGraphQLが不要になるという話をしてる人もいます。 RSCが出てくると、Server-Side-Rendering(SSR)ができる幅が広がってアーキテクチャ的にやりやすくなる話だと僕は一応理解してます。 そうすると、そのSSRの時はAPI呼び出しがサーバー間になるので通信のコストが下がります。
つまりGraphQLを使う必要性が薄れてくるんですね。 この辺りの詳しい話はQuramyさんの記事を参照いただければと思います。
以上のことを考えると、確かにBackend APIがGraphQLである必然性は薄れてくる可能性はあります。 リソース指向でAPIがユースケースベースじゃなくて、ちゃんと細かい粒度でリソースとして取れる必要がある点は同じです。 でも、それはRESTやgRPCでも実現できます。
ただし、パフォーマンスのネックは少なくなるので、fields/includeが必要かというと、そうでもなくなります。 それでGraphQLの優位性がなくなるのかというと、 僕は4つほどあると思っているので一つずつ提示していきます。
GraphQLは、RSCやNext.js App Routerの登場によって代替が可能になるのか
1個目ですね。iOSやAndroidとかで他のアプリを作ることもあると思います。Web Frontend以外を持ち出すのはズルいと思うかもしれませんが、ちょっと聞いてください。 最初はWeb FrontendでNext.jsやApp Routerを使って、2年後ぐらいにアプリも欲しくなった時とかに柔軟に対応できてほしいと思うわけです。
そうすると、後ろがRESTとかの場合はBFFというサーバーを iOS/Android向けに立てたりします。しかし、コンセプトとしてはフロントエンドエンジニアがサーバーも運用していく感じで、やはり運用と開発を両方見ていくのは難しいし、コストは高いと個人的に思っています。 その時に開発の最初からGraphQL Gatewayがあると結構楽できますね。
LayerXさんの発表でもGraphQL Gatewayで作っていく話がありましたが、裏側をGraphQLでAPI提供すると非常に低コストでGraphQL Gatewayを作れます。 なのでこれは推しアーキテクチャですね。 GraphQL API Gatewayは好きで今後もしていきたいパターンですね。
2個目はネットワークレベルのN+1問題を回避できることです。 RSCは細かい単位のコンポーネントでAPIリクエストを送ることが推奨されているので、 ナイーブに実装するとネットワークレベルでN+1が起きて、さすがに DBのN+1とは比較にならないレベルの避けなきゃいけない課題なんです。
Quramyさんの記事によると、Data Loaderと似た考え方を適用するとServer Componentsでもできるみたいです。 本当に皆が回避してくれるかはまだ疑問があって、GraphQLもN+1は出るんですよ。Data Loader Patternで回避できるけど、絶対ではないので心配だったりします。
3つ目としては、APIの進化をさせやすいというのがあります。 バックエンドの人が作ったAPIはずっと一生後方互換性を担保したままにするのは大変です。 エンドポイント単位ならともかく、フィールド単位で使ってないものは消したい ことがあります。
ですが、使われてるのかどうかを調べるのは大変です。 REST APIだと普通にエンドポイントとか叩かれてるけど、フィールドまで使ってるのかちゃんと見に行かないとわかんなかったりします。ただ GraphQL APIの場合はクエリを見るだけで分かるので、バックエンド視点でもありがたいです。
最後はドキュメントとエコシステムについて、ここを満たす何か良い感じのエコシステムはやっぱりGraphQLだと思います。
ここまでのまとめとして、GraphQLの恩恵はサーバー間通信をメインでやっていくと減るかもしれないけど、まだ優位性は残っていると考えています。
全体のまとめとしては、GraphQLを使いたくなるモチベーションについて、バックエンドエンジニアの視点でGraohQLを使ってきたという話をしました。今後もGraphQLのパターンやツールは使われていくと個人的には思っています。
パネリストによるGraphQLについてのディスカッション
――ここからは、運営の方で用意したテーマに沿って、先ほど発表していただいた3名のパネリストによるディスカッションを行います。まず一つ目のテーマは「GraphQLはどういった現場フェーズの会社だと向いていて、どういった活用が想定されるか」です。
内山さんいかがでしょうか?
内山:GraphQLはクライアントの画面の要請に設計の力学を映しやすい技術なので、それにバックエンドの人も応えやすいですね。 大きい会社やチームよりは、 比較的小規模なチームに合うと思っています。
――続けて松本さん、いかがでしょうか。
松本: 自分も内山さんと概ね同じで、0→1の規模が小さい場合は 導入しやすいと思いますね。 先ほどの発表にもあったマルチデバイスとか、うちみたいにプロダクト間の連携があって、いろんなコンテキストでリソースを取得する場合にも非常に良いと思います。
――続いてqsonaさん、お願いします。
qsona: たとえば、今どの現場にReactが向いてるかと言われたら、ほぼ全ての現場ですという答えになると思うんですよ。 別にjQuery使わなくていいからReact使ってくれというのと同じ感覚で、 GraphQLを使ってくれという気持ちです。
――続いてのテーマは「立ち上げでは向いてたり、技術スタック含めて採用するケースがあるけれども、REST APIを採用してるサービスが途中からGraphQLに移行するメリットはあるのか」ですが、 今度はqsonaさんいかがでしょうか。
qsona:SHEのRailsアプリとかはREST APIがあります。ただ、GraphQLをやっていきたいのでちょっとずつGraphQLを増やしてます。 元々RESTだった画面を 機能改善のついでにGraphQLに置き換える感じならいけると思います。
―― 続いて松本さん、いかがでしょうか。
松本: GraphQLのメリットが享受できるなら一気に置き換える必要は無くて、徐々に入れていくのは全然ありだと思います。 GraphQLだから1画面1リクエストという制約は無いので、 リスクと生産性のバランスを考えながら適用していけば良いと思いますね。一般的なリファクタリングの方針の決め方とあまり変わらない気はします。
――続いて、内山さんにもこの辺りをご意見いただければと思います。
内山:既存のAPIを完全に置き換えるよりは、段階的な 移行がセオリーになると思います。 ただ、本当にRESTに明確な問題や課題があって、GraphQLに移行した時にコスト的なメリットが出るかは 懐疑的ですね。少なくとも慎重に考えた方がいいですね。
――続いてのテーマは、「逆にGraphQLを使わない方が良い現場や状況があるか」ですが、 内山さんからお願いします。
内山: なんとなくGraphQLが良いらしいから入れてみました、というスタンスでいくと、予期しない場合の対応とか、悪意あるQueryが投げられたりする場合があるので 覚悟が無いと危ないですね。あとGraphQLエコシステムとの相性があまり良くない言語を使う怖さはありますね。
―― 松本さんはいかがでしょうか。
松本: 自分もほぼ同意見でして、スキーマ駆動開発をしなきゃいけないので、サーバとエンジニアとの距離が遠い状況は合う合わない以前の問題かなと思いますね。 距離が遠いと、スキーマの定義とかの認識をすり合わせるコミュニケーションに結構コストがかかりそうですね。
内山:開発は画面設計から始まることが多くて、実際はDB設計しながらスキーマを考えることが必要だと思っていて、フロントエンドとバックエンドの距離が近い方が 良いスキーマが作れる感覚があります。
――qsonaさんもそのあたりはいかがでしょうか。
qsona:GraphQL APIはユースケース駆動のAPIを作るには向いてないんです。 なので、GraphQLを使えない状況は2個あって、1個はそもそもユースケースでしかAPIを作れないケース。 もう1個は画面設計を見る一方でDBを見ながらリソースを定義しに行く場合で、 使い捨て前提ならユースケースベースの画面にAPIから入った方が楽なケースがあるとは思います。
視聴者からの質問に答える質疑応答タイムへ
――ここからは、視聴者の方からの質問に回答していきます。まず1個目の質問ですね。 「GraphQLでN+1を解決する場合、Data Loaderを使わずにどのように解決するのでしょうか」とのことで、これは内山さんの資料にありましたね。
内山: 基本的にはN+1を解決する場合はData Loaderやその手のバッチ処理が必要になりますが、N+1を放置する方がいることを僕は最近知ったんです。 Data Loaderって実装次第ではオーバーヘッドがそれなりにあるんですね。その待ち時間がN+1にかかるパフォーマンスの悪さとどちらが悪くなるのかをちゃんと計測して、そこにData Loaderを入れるのか入れないかの判断が必要で、場合によってはN+1の方が良くて、Nが2とか3なら大したことないという話がありました。
―― 続いて「認可周りはどうやって実装していますか」ということで、 松本さんいかがでしょうか。
松本: 認可はディレクティブとかで結構やってますが、問題とかは全く無いですね。
―― 内山さん、コメントで「ディレクティブをやってもヒットしなかった」と書いてありますが、実際どうやっているかを最後に聞ければと思いますが、いかがでしょうか。
内山: Resolverの中に入ってからビジネスロジックとして認可を書いたりとか結構泥臭くやってます。認可と言ってもどのレイヤーで止めたいのかが微妙にユースケースによって変わるんです。 なので、場合によってはMutationとかQueryのAuth ErrorみたいなResultを返している部分もあるし、Resolverの中に入ってから 「このユーザーは権限が無い」と言って止めることもあります。認可は割とケースバイケースになりがちで、個別にやるしかないと個人的には思います。
―― 最後の質問です。 「テストはリクエストベースでやるのか、モデル含めてどうテストしてるのか」とのことで、 内山さんからお願いします。
内山:実際にGraphQL QueryやMutationを投げて意図した通りの結果が返ってくるかを、リクエストスペックを書きながら検証しています。 GraphQLのレイヤーとビジネスロジックのレイヤーを分けて、モデルはモデルとしていつも通りテストするように意識しています。
――続けて松本さんいかがでしょうか。
松本:うちも結構似てますが、テストに100%工数を割けてない実態もあるので、 ユースケースとかユースケースのサービスのレイヤーに絞って今テストをしていますね。 なので、GraphQLだからこうしてるという感じではないです。
――続いてqsonaさんいかがでしょうか。
qsona: 基本的にサーバーサイドのテストはリクエストにいかにもフロントエンドが投げそうなクエリを書いてチェックしているので、 結果をアサーションする必要はなくて、型が間違ってたり NOT NULLのはずがNULLになっていたりする場合はリクエストが成功しないんです。
ただ、お悩みポイントとしては投げる側です。 タイプごとのテストはちょっと書きにくいので、ペアレントオブジェクトを指定してタイプごとにテストを書いて、 1段階目までちゃんとresolveされれば良しとしています。
――それでは最後に株式会社overflowさんからのお知らせをお願いいたします。
大谷:弊社では、ITエンジニア、デザイナーの副業転職サービスOffersを提供しております。現在登録者限定で過去のOffersのイベントアーカイブ動画も全て見放題となっております。
また、開発生産性を可視化するサービスOffers MGRも提供しております。あらゆる開発ツールとデータ連携を行うことで、組織のコンディションや現場の状態把握が可能となります。
14日間無料のトライアルプランもリリースしております。ぜひこの機会にご活用ください。
――本日のイベントはこちらで終了とさせていただきます。 登壇者の皆さん、視聴者の皆さんありがとうございました。