🙆

GraphQL Federation(Apollo Federation) の subgraph 運用時の落とし穴とその対策

に公開

この記事は、株式会社エス・エム・エス Advent Calendar 2024 シリーズ2の12/24の記事です。
https://qiita.com/advent-calendar/2024/bm-sms

こんにちは、介護/障害福祉事業者向け経営支援サービス「カイポケ」のリニューアルプロジェクトで居宅介護支援サービスの開発を担当している李です。担当しているサービスは他の複数のサービスと連携していますが、弊社は GraphQL を使って API を実現し、Apollo Federation を採用して複数のサービス(subgraph)を連携しています。その中で subgraph の効率的な運用が成功の鍵を握ります。各 subgraph は独立したチームが開発を進められる一方で、全体として統一性を保つための工夫が重要です。
複数の subgraph を運用する際には、同じドメイン(業務領域)に対して同名な type、enum、input などを定義することがよくあります。しかし supergraph へ compose する際に一定のルールを守らないとエラーが発生したり、予期しない動作を引き起こす可能性があります。
この記事は subgraph に同名の定義が存在する場合の隠れた問題(気づきにくい落とし穴)や、その対処方法をご紹介します。もし subgraph の運用に関する知見をお持ちであれば、ぜひ共有いただけると幸いです。

そもそも課題は

Apollo Federation によって複数の subgraph を supergraph へ compose する場合、通常同名の定義に対しては整合性問題がある場合 compose エラーが発生すると検知できます。しかし、Apollo Federation Composition Rules によって、一部の整合性問題があってもエラーが発生せずに(compatible とされるため) compose できて、結局想定外の supergraph が生成されてしまう可能性があります。その結果、実際に supergraph を使う時に想定外の動作になるか、エラーが起きてしまって不具合が発生してしまいます。

結論(対策方針)

先に結論から言いますと、複雑なApollo Federation Composition Rulesを一々意識すると運用のコストが高いため下記のようなシンプルな方法に倒して対策することをお勧めます:

  • 複数の subgraph に意図的に同名なものを定義する場合、全く同じ構成(match exactly)で定義します
    • compose rules によると全く同じ構成にすると supergraph も同じ構成で生成されるため想定外の結果にならないです
  • 複数の subgraph は異なる構成のものを定義する場合、異なる名前にします
    • 意図せず同名に定義してしまったことを回避するために、それを検出する仕組み(↓検証方法)によって検出され異なる名前に変更します
    • 異なる名前に変更する際に Prefix を付けるなどで工夫します(ref

検証方法

対策方針の通り、同名且つ差分がある場合は検出して異なる名前にする必要があります。
同名且つ差分がある場合 subgraph を compose する時に警告(Hints)が出されています。このような警告を検知する仕組みを CI に組んで、検出された場合は CI で落とすようにします。そのあと異なる名前に変更して CI が通るようにします。

事例

同名定義によって発生する問題が様々ですが、ここで理解を深めるために簡単に2つの事例を紹介します。

事例1: enum

下記のように2つの subgraph に同名の enum 定義があります。この事例は inconsistent_enum_value_for_output_enum に当てはまります。

"subgraph A"
enum color {
  RED
  BLUE
}
"subgraph B"
enum color {
  RED
  BLUE
  GREEN
}

上記のような場合、生成した supergraph はどうなりますか?結論から言うと確定した結果がなく、状況次第です。具体的な composition strategy は下記のようになります(2024/12/02 現在出典

結果、想定外の結果が生まれて想定外の動作になる可能性もあります。下記のユースケースを考えましょう:

  • enum color を戻り値として使う前提にしましょう( Union Strategy に当てはまります)。最初は subgraph A で enum color を戻り値だけとして使いました。その時にクライアントは RED/BLUE だけを受け取ります。しかし、subgraph B で enum color を追加した後に supergraph を作成すると RED/BLUE 以外に GREEN も追加されます。それによって RED/BLUE/GREEN を返すので、RED/BLUE だけ受け取りたいクライアントにとって GREEN は想定外の結果になります。

事例2: input

下記のように2つの subgraph があり、同名の input 定義があります。この事例は inconsistent_but_compatible_field_type に当てはまります。

"subgraph A"
input UserInput {
  id: ID!
  name: String!
  "必須ではない"
  age: Int
}
"subgraph B"
input UserInput {
  id: ID!
  name: String!
  "必須"
  age: Int!
}

上記のような場合は生成した supergraph は下記になり、必須に寄せた結果になります。

"supergraph"
input UserInput {
  id: ID!
  name: String!
  "必須"
  age: Int!
}

上記のような結果になると想定外の動作になる可能性があり、実際に弊社では同じようなパターンの不具合が発生しました。
少し詳しく言いますと、subgraph Aが先に存在していて、age というフィールドが nullable なため、クライアントから設定しない場合があります(age = null)。その後、subgraph B に上記の事例のような同名の UserInput も追加され、それによって supergraph のような UserInput が生成され、age が non-nullable になって設定必須になります。こうなると、クライアントから age に設定しない(null を渡す)ままだと graphQL のエラーが起きてしまいます。想定外の結果になってしまいます。

まとめ

いかがでしょうか。Apollo Federation を利用する際、サービスを連携することで便利さがありますが、一方で subgraph の compose ルールがやや複雑であるため、いかにシンプルに管理するか工夫が求められます。今回は、subgraph に同名の定義が存在する場合の隠れた問題(落とし穴)についての対処方法をご紹介しました。ぜひお気軽にお試しください。

株式会社エス・エム・エス

Discussion