こんにちは、ギフティでエンジニアをしている @memetics10 です。
ギフティでは、事業や組織の拡大とともにシステムごとの責務が見直され、結果としてシステムがより細かく分割されることも増えてきています。 そして時には、複数の内部システムを統合することで 1 つのプロダクトとして外部提供するケースも生まれています。 分散的なシステムの統合には様々な技術的課題がありますが、今回は参照系統合のソリューションとして Apollo Federation を紹介します。
参照系統合のいくつかの手段
ギフティでは、多くのケースでそうであるように、システムごとに DB schema や API schema が分かれています。 このように複数に分かれたシステムを利用して単一のプロダクトを提供したいとき、クライアントサイド(フロントエンド)には参照系統合に関するいくつかの選択肢があります。
1. クライアントがバックエンドに直接リクエストする
まずは、クライアントサイドが自身で複数のバックエンド API を叩き分ける方法が考えられます。 これは構成要素が最小限であるという点で優れていますが、クライアントにとって単一の API schema が存在しないことによるデメリットもあります。 例えば、複数 API から取得したデータを合成するための処理をクライアントサイドで書かなければならないのは当然のことながら、合成後の型定義も自前で行わなければなりません。 この問題は一見瑣末に思えますが、複数クライアントが利用する場合は各クライアントごとにデータの合成を個別で実装することになり、より大きな問題となります。 また別の問題として、バックエンドの変更がクライアントに波及しやすくなることも考えられます。 例えばバックエンドのシステム粒度を変更するようなことがあれば、クライアントサイドは API コールの大きな修正を余儀なくされます。
2. BFF を実装する
別の選択肢として、クライアントとバックエンドの間に BFF(backend for frontend) を実装する方法が考えられます。 この方法では、クライアントがバックエンドに直接リクエストした際の問題が解決されますが、代わりに構成要素が増えることによるデメリットがあります。 データの合成が必要な箇所は一部分に限られ、実装のほとんどがバックエンドの API をプロキシするだけの場合は、得られる恩恵が管理コストに見合わないことも考えられます。 また、バックエンド API に変更が入ると必ず BFF の変更も必要になるため、アジリティを損なう懸念もあります。 さらに言えば、GraphQL API の場合は、N+1 問題のようなパフォーマンスの問題に対処することも難しくなるでしょう。
3. GraphQL Federation
最後に、GraphQL Federation と呼ばれる方法を紹介します。 GraphQL Federation とは、分散システムにおいて複数の GraphQL API を 1 つの統一された GraphQL スキーマとして提供するためのアプローチです。 GraphQL Federation では、統合対象の GraphQL API の仕様に基づいて 単一のGraphQL API への統合が自動的に行われます。したがって、データの合成やプロキシの処理を書く必要がなく、API の拡張や統合の作業を最小限に抑えられます。 また、実行時のクエリ最適化も自動化されており、たとえば Apollo Federation には Handling the N+1 Problem が用意されていたり、 Query Plans という機能により最適なクエリ実行を行うことができたりします。
Apollo Federation の採用
私が今開発している新規プロダクトでは、クライアントサイドが複数のバックエンドからデータを取得する必要がありました。 このバックエンドのシステム粒度は比較的不安定であり、変更をクライアントに波及させないために単一の API schema を提供したかったのですが、BFF の自前実装は実装や管理にコストがかかるので今回は見送り、実験的に GraphQL Federation の方式を試してみることにしました。
GraphQL Federation を実現するにあたり利用したのは、Apollo Federation です。 この他にも、bramble のような仕様・実装がありますが、Apollo Federation がデファクトスタンダードであるように見受けられます。 また余談ではありますが、Open Federation というオープンな GraphQL Federation の仕様を決める動きもあるようです。
Apollo Federation の使い方
Apollo Federation は一見仰々しく複雑な仕組みに思えますが、実際のところ簡単に使うことができます。 というのも、一般的な BFF と異なり具体的な実装が不要なため、基本的にやることは以下の 3 つの設定を行うだけです。
- バックエンド API のスキーマに Apollo Federation のディレクティブを追加
- 複数のバックエンド API のスキーマに基づいて単一の API schema を自動生成
- 単一の API schema に基づいた API を提供するためにサーバーを起動
具体的な構築方法は公式ドキュメントを参照いただきたいですが、ここでは Apollo Federation がいかに簡単に使えるかを説明するために、少しだけ手順を紹介します。
まず Apollo Federation を使うには、バックエンドの API(Apollo Federation の文脈ではサブグラフと呼ぶ)に Apollo Federation 用のディレクティブを追加する必要があります。 Apollo Federation は、このディレクティブに基づいてスキーマの統合を行います。 サブグラフとして利用できる GraphQL のライブラリはFederation-Compatible Subgraph Implementations に列挙されており、ギフティでは gqlgen を利用しているため、以下のような設定を書くだけで Apollo Federation のディレクティブが有効化されます。
federation: filename: graph/federation.go package: graph version: 2
なお、ディレクティブの詳細については Apollo Federation Directives を参照してください。
さて、続いては複数のサブグラフを統合して単一の API schema を構築する必要があります。 この単一の API schema を、Apollo Federation の文脈ではスーパーグラフと呼びます。 スーパーグラフの構築には、Apollo GraphOS という Apollo が提供するマネージドサービスを使う方法と Rover という CLI ツールを使う方法があります。 Apollo GraphOS では サブグラフの管理・スーパーグラフの検証・構成・更新を含む CI/CD 等を行うことができ、本番環境ではこちらを利用することが推奨されていますが、ギフティでは Rover を使う方法を選択しました。この選択に関しての詳細は後述します。
スーパーグラフを構築したら、最後にサーバーを立ち上げます。 これを Apollo Federation の文脈ではルーターと呼びます。 ルーターはスーパーグラフのスキーマを食わせて起動するだけで、有効な GraphQL エンドポイントを提供してくれます。 ルーターの選択肢として、Apollo Router と Apollo Server がありますが、Apollo は Apollo Router を強く推奨しているようです。 また、Apollo Router のホスティングには Rust のバイナリをセルフホストする方法と、 Apollo GraphOS にホストする方法があり、スーパーグラフと同様に本番環境では Apollo GraphOS の利用が推奨されています。 Apollo GraphOS を使うと先述のスーパーグラフ構築を含めた CI/CD やモニタリングやプロビジョニングをしてくれるのですが、ギフティではルーターを起動する Docker image を作って AWS Fargate でホスティングすることにしています。
Apollo Federation を使ってみてわかったこと
まず第一に、Apollo Federation は思った以上に簡単に使えることがわかりました。 もちろん、ディレクティブ等 Apollo Federation 特有の概念に対する理解が必要な箇所はありますが、構築自体にはほぼ作業が要らないため、BFF 実装の手間を省く当初の目的はすでに達成できています。 特に GraphQL を普段から使っている方であれば、Apollo Federation の利用を本格的に視野に入れてみることをおすすめしたいです、
先述の通り、Apollo Federation を採用したギフティのプロダクトでは Apollo GraphOS を利用せず、Rover CLI を使ってスーパーグラフを構築し、Apollo Router を Fargate で起動しています。 この選択がどういった結果に結びつくかは、現時点では明確な答えが出ていません。 バックエンドが AWS でホスティングされているのもあり、利用するクラウドサービスをむやみに増やしたくなかったことからこの選択をしましたが、Apollo Federation を適切にデプロイするためのフローは若干複雑なため CI/CD を自前で構築するのは大変ではあります。 Apollo Federation の実験的な導入フェーズを終えたら、Apollo GraphOS への乗り換えも検討に入れたいと考えています。
さいごに
Apollo Federation を利用すると簡単に分散システムの参照系統合を行うことができますが、個人的に Apollo Federation が優れていると思ったのは、コンテキスト境界を考えるツールにもなる点です。 スーパーグラフの構築はサブグラフ間の関係に不整合があると成功しないので、この整合性をとるために、必然的に明確なコンテキスト境界を検討する必要が生まれます。 国内ではあまり採用事例を見ない Apollo Federation ですので、この記事が少しでもお役に立てば幸いです。