Closed10

Apollo Federation を理解したい

Hidden comment
genkey6genkey6

概念理解

https://netflixtechblog.com/how-netflix-scales-its-api-with-graphql-federation-part-1-ae3557c187e2

  • Why GraphQL Federation?
    • Netflix の記事が有名っぽいので読んでみる
    • The goal of GraphQL Federation is two-fold: provide a unified API for consumers while also giving backend developers flexibility and service isolation. To achieve this, schemas need to be created and annotated to indicate how ownership is distributed.
    • (gateway の裏側にいる microservice を1段階まとめる単位をどのように分割するかが肝になりそう)
  • architecture components
    • gateway = (Apollo 公式ドキュメントでいう supergraph)
    • DGS (Domain Graph Service) = (Apollo 公式ドキュメントでいう subgraph)
    • schema registry
  • key concepts in implementation
    • schema composition
    • query planning and execution
    • entity resolver
genkey6genkey6

Apollo 公式ドキュメントを読んでみる

Federated Schemas

Schema composition

https://www.apollographql.com/docs/federation/federated-types/composition

  • subgraph の schema を supergraph schema に合成する
  • 異なる subgraph 間で同じフィールドに対して異なる定義をしている場合は validation error になる
    • ただし optional かどうかが異なるだけなら OK → この場合 nullable になる
    • @shareable を付けると複数 subgraph で同一フィールドを定義できるようになる
  • If multiple subgraphs define the same type, each field of that type must be resolvable by every valid GraphQL operation that includes it.
    • subgraph 間での互換性を気にする必要がある
  • @key を付けて type を Entity として扱うようにすると、1つの type に関する定義を異なる subgraph 間で分割してできる
    • This is a useful solution when a type corresponds closely to an entry in a data store that one or more of your subgraphs has access to (e.g., a Users database).
  • merge の戦略は Union (全部含める) と Intersection (共通項を取る) の2つが存在 → どちらを採用するかは GraphQL の型に依存
    • object, union, interface は常に Union
    • input, field arguments は常に Intersection
    • Enum の場合は使われ方による
    • (受け取る値は厳しく/返す値は緩く、という方針)
genkey6genkey6

Entity

https://www.apollographql.com/docs/federation/entities/

an entity is an object type that can resolve its fields across multiple subgraphs.

To fully define an entity within a single subgraph, you do the following:

  1. Assign the entity a @key
  2. Define the entity's reference resolver

reference resolver とは

The @key directive effectively tells the router, "This subgraph can resolve an instance of this entity if you provide its primary key." In order for this to be true, the subgraph needs to define a reference resolver for the entity.

  • Entity に対して1つでも独自のフィールドを定義している subgraph は reference resolver を実装する必要がある
    • どのように reference resolver を定義するかは利用しているライブラリによって異なるので個別に調査が必要
  • Entity を参照するだけなら schema 内に stub を定義すれば reference resolver の実装を省略できる
    • @key を指定 & resolvable: false として ID のみの型を定義しておくと query plan 時に __typename, id を元に別の subgraph が実装している reference resolver を呼び出して必要なフィールドを解決してくれる
genkey6genkey6

federation 特有の directive 定義

https://www.apollographql.com/docs/federation/federated-types/federated-directives

  • 型定義
    • @key: Entity を一意に特定するキーを指定する
    • @extends: 型の拡張を行う場合に指定する
      • federation 2 では利用しない
  • フィールドの共有
    • @shareable: あるフィールドが複数の subgraph によって解決可能であることを示す
    • @inaccessible: supergraph の schema から除外したいフィールドに対して指定する
      • @shareable なフィールドを段階的に実装する際や client に対して private なフィールドを @key の一部として扱いたい場合に利用する
    • @override: ある subgraph で定義していたフィールドを別の subgraph で定義し直していることを示す
      • subgraph 間でフィールド定義のマイグレーションを行う際に利用する
  • フィールドの参照
    • @external: その subgraph では対象のフィールドを解決できないが、何らかの理由で型としては定義しておきたい場合に指定する
      • @provides@requires と組み合わせて利用する
    • @provides: 特定の schema の path でのみ解決可能なフィールド群を指定する
      • クエリの最適化のために利用する
    • @requires: あるフィールドの解決に他の subgraph で定義されているフィールドの解決が先に必要な場合に指定する
      • ここで指定されたフィールド群は client がリクエストしていなくても router 側でリクエストが行われる
  • メタデータの定義
    • @tag: 任意の string のメタデータを指定したフィールドに付与する
  • custom directive の利用
    • @composeDirective: subgraph で定義した custom directive を supergraph の schema でも残したい場合に指定する
      • デフォルトでは supergraph の schema に composition する際にほとんどの directive は除外される
genkey6genkey6

Router

https://www.apollographql.com/docs/router/

https://zenn.dev/ryo_kawamata/articles/try-apollo-federation

genkey6genkey6

Rover

https://www.apollographql.com/docs/rover/

  • 独自の CLI ツールが提供されており schema 管理に活用できる
    • Apollo Studio. This is the primary web interface for GraphOS. Studio helps you monitor, manage, and collaborate on your supergraph.
    • The Rover CLI. This is the primary command-line interface for GraphOS. Rover helps you interact with your graphs and their schemas.
  • CI に組み込んで schema のバリデーションを行ったり: https://www.apollographql.com/docs/rover/ci-cd
  • subgraph/supergraph それぞれを操作するためのコマンドが用意されている
    • 例: rover supergraph compose --config PATH_TO_CONFIG_FILE で supergraph schema の composition ができる
genkey6genkey6

TypeScript

  • NestJS + @apollo/subgraph package で実装
  • reference resolver は @ResolveReference デコレータを付与したメソッドで定義する
  • 各種 directive は @Directive デコレータで付与する
  • autoSchemaFile オプションに federation: 2 を指定して生成した schema 定義ファイルに directive が上手く反映されていなさそうな点が少し気になった
    • 実際に supergraph 経由でリクエストを送ってみると Entity の解決は上手くいっていた → 今回は supergraph の composition 時に subgraph の schema を動的に読み取りにいくように実装したため、生成された schema 定義ファイルは参照されていない

Rust

  • async-graphql が Apollo Federation 対応しているので採用
  • GraphQL サーバーの実装について
    • https://zenn.dev/mkazutaka/articles/9b9228da5a741a が参考になった
    • GraphQL schema を抽象化した Schema 型があって Query, Mutation, Subscription をそれぞれ登録していく
    • SimpleObject trait を derive するとその型のフィールドは自動的に GraphQL の type にマッピングされる
    • #[Object] attribute を付与した型はフィールドごとに手動で resolver を定義する必要がある
      • root となる Query, Mutation はこれで定義する
  • federation 関連
    • #[graphql(hoge)] という attribute をフィールド単位に付けていくことで各種 directive の機能を担保する形式
    • #[graphql(entity)]@key directive が付与され reference resolver が実装される
    • 他の各種 directive も同じように定義できる: #[gaphql(shareable)]@shaeable, #[graphql(extenal)]@external など

Python

  • strawberry-graphql が Apollo Federation 対応かつ code first に実装できるので採用
  • dataclass を拡張したデコレータで GraphQL の type 定義を行う
  • コマンドで schema 定義ファイルの出力もできて便利
  • federation 関連
    • 通常は @strawberry.type といったデコレータを用いるところを federation に対応させる場合は @strawberry.federation.type といった形で定義していく
    • reference resolver はクラスメソッドで定義する
    • 各種 directive はデコレータの引数で指定することで付与できる

一通り実装してみての感想

  • Entity がコアな概念なのでちゃんと理解することが重要
    • reference resolver が少し難しかったが「router から ID (= Entity を一意に特定する情報) が与えられた際に、その subgraph 内で対象 Entity をどのように解決すべきかを定義するもの」と捉えると理解できた
  • schema 定義の際に supergraph first にするか subgraph first にするか問題
    • ゼロから作る際には、前者の方が全体であるべき姿を描いてから最適な分割方法を設計できるので良さそうだが、現実には既に運用されている複数の GraphQL サーバーを subgraph として schema composition するケースも多そう
    • 後者の場合それぞれの subgraph を別々のチームが開発する前提だと、同一 Entity のフィールド定義に関して複数 subgraph が責務を負うケースでフィールド間の調整が大変そうなので CI でバリデーションを行うなどの工夫が必要 → こういったケースで Rover を活用すると良さそう
      • この辺は実運用で上手くやっている事例があったら知りたい
このスクラップは2023/01/14にクローズされました