この記事は、Money Forward Engineering Advent Calendar 2021 20日目の投稿です。
こんにちは、マネーフォワード Pay for Businessを開発するPay事業本部の開発者のダンと申します。
前回はGraphQL Federation - API Gatewayの進化の記事にGraphQL Federationを採用することでいいマイクロサービスアキテクチャが実現できたことについて紹介しました。この記事を読む前に、前回の記事を事前に読むことをおすすめます。
まずGraphQL Federation - API Gatewayの進化で紹介した内容を復習します。
- GraphQL Federationの利点は:
- バックエンド側に複数マイクロサービスが提供した複数のGraphQL APIsはGraphQL Federation仕組みにより一つのサービスの一つのGraphQL APIとしてクライアントに提供します。
- 各サービスはFederationの仕様に沿って実装すれば良くて、Gatewayはビジネスロジックがなく、主にルーティングの役割でクライアントからリクエストを適切なサービスに送ります。
- 上はFederation仕組みに与えた利点ですが、名前の通りもちろんGraphQL自体の利点全てあります。
この記事にはGraphQL Federationは上の利点を得るのためどのように動かすかということについて説明させていただきます。
記事の構成
- GraphQL Federationのアーキテクチャー
- 各サービスのsubgraphを作成します
- subgraphsから一つのsupergraphを作成します
- Gatewayでクライアントからのリクエストをどのように解決ますか
- 最後に
1. GraphQL Federationアーキテクチャー
GraphQL Federationアーキテクチャーを見ていきましょう。
- 図の一番下はマイクロサービス群です。各サービスが提供するGraphQL APIはsubgraphと呼ばれます。そのsubgraphはGraphQL仕様を拡張するApollo Federation subgraph 仕様に沿って実装されました。
- 今回の例は三つのサービスの三つのsubgraphがあります。
- 次はApollo GraphQLのOSSのGatewayです。Gatewayにあるsupergraphはマイクロサービス群が提供したsubgraphから作成されました。そのためGatewayは一つのgraphだけでクライアントに提供して、クライアントは一つのサービスのようなものが見えるようになります。
2. 各サービスのsubgraphを作成します
このパートはFederationはどのように一つのGraphを作成するのかということを説明していきます。まず、各サービスがFederation仕様により作成されるsubgraphの形から見ていきます。
通常のGraphQL 仕様と同じくusers、products、reviewsはサービス自体担当しているUser、Product、ReviewというGraphQL Typeがあります。
usersサービスのUser Typeはid, nameの二つのフィールドが定義されますが、あるユーザーが最近買ったプロダクトは何にかを知りたい時どうしますか?そもそもProduct Typeとプロダクトのデータはproductsサービスにありますので、すぐ思い付くことはUser Typeに最近買ったプロダクトのrecentPurchasesというフィールドを直接に追加して、そのフィールドのresolverにproductsサービスに叩いてデータを取得するでしょう。
ですが、上のようなことをやると、productsサービスでusersサービス向けそのAPIを作成しないといけないです。recentPurchasesはproductsサービスのデータですので、productsサービスを直接に取得できればとてもいいではないか。それはFederation仕様により各サービスのsubgraphが作成されたら実現できるようになります。
users subgraphのUser TypeにFederationのカスタマイズの@key directiveでidという識別子を定義します。recentPurchasesフィールドをUser Typeに追加したくて、recentPurchasesはproductsサービスに取得したいので、products subgraphにGraphQLのextendでUser Typeを拡張して、そのTypeのidフィールドにも@externalというFederationカスタマイズdirectiveで、この識別子は別のsubgraph(users subgraph)の識別子ということを報告します。
それで、userのレビューを取得したり、productに対するレビューを取得したりしたければ、上のような作業を続けて、products subgraph、users subgraph、reviews subgraphが出来上がりました。
3. subgraphsから一つのsupergraphを作成します
これまで、各サービスのsubgraphが作成できました。このsubgraphたちはどのように活用されますかここに説明させていただきます。
図を見ると、supergraphを作成するため、マイクロサービスの全てのsubgraphが必要になります。User Typeは初めにusers subgraphで定義されて、productsとreviewsのsubgraphsで拡張されましたので、supergraphで一つのUser Typeで全部のフィールドが揃ってきました!同じくProduct TypeとReview Typeもsuper graphで作成されて、サービスの全体は綺麗に表まました。このsupergraphはもちろんsubgraphたちが定義した情報を全部持ちます。
4. Gatewayでクライアントからのリクエストをどのように解決ますか
マイクロサービス群の全体のsupergraphを作成できました。クライアントはこのgraphを見てリクエストを送って、リクエストはFederationにはどのように処理されるのか見ていきましょう。
Gatewayはクエリを受け取って、大きくに二つのステップを行います:
Query Planning
- Gatewayはsupergraphに沿ってクエリをパースして、実行計画を作ります。それにより下の質問を答えられます:
- フィールドはどのsubgraphにあるのか?
- そのフィールドを取るため何をしないといけないですか?
- Gatewayはsupergraphに沿ってクエリをパースして、実行計画を作ります。それにより下の質問を答えられます:
Execution
- ステップに出来た計画により実際にsubgraphを担当するサービスたちへクエリを送ります。
早速、一つのリクエストを作成してGatewayに投げましょう。
GetCurrentUserReviewsはユーザーの名前(name)とユーザーのレビュー(reviews)とユーザーが最近買ったもの(recentPurchases)を取得するクエリです。
まず、クエリに親のcurrentUserフィールド(と子フィールドのname)はUser Typeですので、Gatewayは二つのフィールドを切り出して、かつ子フィールドのidも追加して、users subgraphに送ります。
usersサービスからもらったuser idを使って、クエリの残りのフィールドをproductsとreviewsサービスに送ります。productsサービスでuserのidで最近ユーザーが買ったプロダクトを取得できて、reviewsサービスもユーザーが書いたレビューを取得できます。
最後に、Gatewayは三つサービスからのレスポンスをアグリゲートして一つのレスポンスをクライアントに返します。 実行の流れを全体的に揃うと以下の形になります。
右の赤いかこは「一連の実行」の起票されている通り、このGetCurrentUserReviewsクエリを実行するためGatewayは最初にusers serviceを叩いて、その後他のサービスを叩きます。 左の紫かこは「平行な実行」の通り、Gatewayからproductsとreviewsサービスへのクエリは平行に行われます。
5. 最後に
以上はGraphQL Federationはどのように動かしますか紹介しました。GraphQLとGraphQLを拡張したFederation仕様は綺麗なマイクロサービスアーキテクチャーが出来ましたね!
次回でできればチームで本番にどのように採用して、運用しましたか紹介していきたいです。
ありがとうございました!
マネーフォワードでは、エンジニアを募集しています。 ご応募お待ちしています。
【サイトのご案内】 ■マネーフォワード採用サイト ■Wantedly ■京都開発拠点
【プロダクトのご紹介】 ■お金の見える化サービス 『マネーフォワード ME』 iPhone,iPad Android
■ビジネス向けバックオフィス向け業務効率化ソリューション 『マネーフォワード クラウド』
■だれでも貯まって増える お金の体質改善サービス 『マネーフォワード おかねせんせい』