Requirements: gRPC/Connect RPC Service → GraphQL Operations + Federation Subgraph
概要
protoc-gen-pothos を拡張し、gRPC (Connect RPC) の Service/RPC 定義から GraphQL の Query/Mutation フィールドを resolver 実装込みで生成する。生成された GraphQL スキーマは GraphQL Federation の subgraph として運用できる(@key による entity 解決、BatchGet RPC からの dataloader 生成、他 subgraph が所有する entity への拡張)。
2 段階でリリースする:
- Step 1: RPC → Query/Mutation resolver 生成
- Step 2: Federation 対応(entity / dataloader / entity 拡張)
dataloader 生成プラグイン(protoc-gen-dataloader)は Step 2 の依存物だが、federation 対応と独立して開発可能なため要求・設計を別ドキュメントに分離している。
用語
- 公開 RPC:
(graphql.service)オプションでオプトインされたサービスに属し、(graphql.rpc).ignoreされていない unary RPC - batch RPC:
(graphql.rpc).batchが宣言された BatchGet 系 RPC(dataloader 生成対象) - entity resolver RPC: batch RPC のうち
(graphql.rpc).federation.entity_resolverが宣言されたもの - extend RPC:
(graphql.rpc).federation.extendにより既存型のフィールドとして公開される RPC
Step 1: RPC → Query/Mutation
R1. オプトインと対象選別
- R1.1: システムは
(graphql.service)オプションが付与されたサービスの RPC のみを生成対象とすること(付与されないサービスは従来通り無視) - R1.2:
(graphql.rpc).ignore = trueの RPC は生成対象から除外すること - R1.3: streaming RPC(client/server/bidi)は生成対象外とし、警告を出力すること。明示的に
operationが指定されている場合はエラーとすること - R1.4: 本機能は
protobuf_lib=protobuf-es(v2)でのみ有効とすること。他ランタイムで(graphql.service)を検出した場合はエラーとすること
R2. operation 種別の決定
- R2.1:
(graphql.rpc).operationが指定されていればそれに従うこと(QUERY / MUTATION) - R2.2: 未指定の場合、
idempotency_level = NO_SIDE_EFFECTSの RPC は Query、それ以外は Mutation として生成すること
R3. フィールド名
- R3.1: デフォルトのフィールド名は RPC 名の camelCase とすること(
GetUser→getUser) - R3.2:
(graphql.rpc).nameで上書きできること
R4. 引数
- R4.1: Query の場合、request message のフィールドを GraphQL 引数として flatten すること(ネストした message フィールドは既存の
FooInput型を参照) - R4.2: Mutation の場合、単一の
input引数(request message 由来の Input 型)とすること - R4.3: 引数の nullability は既存のフィールド nullability 規則(明示オプション > proto3 optional > behavior comment 等)に従うこと
R5. Request/Response の型変換
- R5.1: file レベルオプションにより、RPC の
XxxRequestmessage から suffix をInputに変換した Input object を生成できること(ignore_requestsの発展形) - R5.2: 同様に
XxxResponseから suffix をPayloadに変換した object type を生成できること(ignore_responsesの発展形) - R5.3: デフォルトの戻り値は response message 由来の型(変換有効時は
XxxPayload)とし、構造を保持すること - R5.4:
(graphql.rpc).expose_fieldにより response 中の指定フィールドを戻り値として unwrap できること(暗黙の自動 unwrap は行わない) - R5.5:
google.protobuf.Emptyは request 側では引数ゼロ、response 側ではBoolean(常にtrue)として扱うこと
R6. resolver 実装
- R6.1: resolver は完全な実装を生成すること(ユーザーによる resolver 記述を不要とする)
- R6.2: resolver は GraphQL context 上の規約キーから Connect Transport を取得し、runtime パッケージの
getClient(ctx, Service)(サービス毎 memoize)経由で client を得ること - R6.3: per-request のヘッダ等は context 規約上の callOptions hook から伝搬できること
- R6.4:
ConnectErrorはGraphQLError(extensions.codeに Connect の code 名)へ変換して throw すること。エラー詳細(details)はデフォルト非掲載とし、変換関数は runtime パッケージで差し替え可能とすること
Step 2: Federation
R7. entity 宣言(@key)
- R7.1:
(graphql.object_type).federation.key(repeated、fieldset 文字列)により entity を宣言できること - R7.2: fieldset は proto フィールド名で記述し、生成時に GraphQL フィールド名へ変換すること(
(graphql.field).nameと整合) - R7.3: entity は Pothos の
asEntityとして生成され、@pothos/plugin-federationを登録した builder でtoSubGraphSchema()により subgraph スキーマを構築できること
R8. entity 解決(resolveReference + dataloader)
- R8.1: batch RPC に
(graphql.rpc).federation.entity_resolverを宣言することで、その RPC を entity の解決手段にできること - R8.2: entity resolver RPC は、
operationが明示されない限り Query フィールドとしては公開しないこと - R8.3: resolveReference は protoc-gen-dataloader が生成する per-context キャッシュの loader を経由し、同一リクエスト内の representation をバッチして BatchGet RPC を呼ぶこと
- R8.4: entity の
@keyと loader のキー(batch.entity_keyまたは推論結果)の整合を codegen 時に検証すること - R8.5: 照合はキーマッチ方式であること(詳細要求は protoc-gen-dataloader 設計 §3.3 に定義: 順序非依存、欠損 null、エラー全キー伝搬)
R9. entity 拡張・リレーション(RPC のフィールド化)
- R9.1: 他 subgraph 所有の entity は、キーフィールドのみを持つスタブ message +
(graphql.object_type).federationの extends 宣言で表現できること(Pothos のexternalRefとして生成) - R9.2:
(graphql.rpc).federation.extendにより、RPC を指定型のフィールドとして公開できること(親のキーフィールド値 → request フィールドの対応を宣言) - R9.3: extend 対象はスタブ message(外部 entity)とローカル message(自 subgraph 内リレーション)の両方をサポートすること
横断要求
R10. proto 拡張オプション
- R10.1: 新オプション(
(graphql.service),(graphql.rpc)(batchを含む),GraphqlFederationOptions等)は proto-graphql 本家リポジトリのgraphql/schema.protoに experimental 明記で追加すること(field number 2056 慣例、Go binding 同時再生成、submodule 更新後pnpm gen:extensions)
R11. テスト
- R11.1: golden test framework に service 付き testapis proto を追加し、コード snapshot / 型チェック / SDL snapshot を検証すること
- R11.2:
createRouterTransportによるフェイク Connect サーバを用い、query.graphql実行スナップショット(既存の休眠経路)で resolver の実行時挙動を検証すること - R11.3: Step 2 では
_entitiesクエリの直接実行と@apollo/compositionによる compose 可能性を検証すること
R12. 後方互換
- R12.1: 新オプションを使用しない既存ユーザーの生成結果は一切変化しないこと
スコープ外(将来課題)
- Subscription(server streaming RPC の変換)
- ts-proto / protobuf-es v1 でのサービス生成(設計上の拡張余地は残す)
@shareable/@requires/@provides/@override等の高度な federation directive(federationネスト下に将来追加)- NOT_FOUND → null 変換(エラー変換 hook で各自対応可能)
- protoc-gen-gqlkit(本 PoC の知見を踏まえた将来プロジェクト。decision-log.md §6 参照)