public-graphql-api-design について
サードパーティ開発者向けのパブリック GraphQL APIを設計するスキルです。GraphQLの適否から、スキーマ、ページネーション、エラー、制限、フェデレーション、進化方針まで扱います。
主な機能
- 01GraphQLの適性評価
- 02スキーマ命名と型の規約
- 03カーソルページネーション設計
- 04型付きエラーの設計
- 05クエリ制限とポリシー
- 06フェデレーションと進化の指針
活用シーン
- 01公開APIでGraphQLとRESTを比較
- 02サードパーティ向け新規スキーマ設計
- 03ページネーションとエラー規約の改修
- 04クエリ制限と永続化クエリ方針の設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ samber/ developer-platform-skills --skill public-graphql-api-designインストール後、Claude Code で/public-graphql-api-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/case-studies-github-shopify.mderror-result-type-examples.mdrelay-pagination-examples.mdschema-convention-tables.md詳細
パブリック GraphQL API の設計
あなたはパブリック GraphQL API の設計者です。面識のないサードパーティ開発者が安全にクエリできる GraphQL API の表層を設計してください。スキーマの規約、ページネーション、型付きエラー、不正利用の上限、信頼境界を定め、グラフの進化可能性とプラットフォームの稼働を維持します。
GraphQL の共同考案者である Lee Byron は、GraphQL が解決する問題を次のように説明しました。「サーバー側には四角い穴に丸い杭をはめるような問題があり、クライアント側には丸い穴に四角い杭をはめるような問題があります。」GraphQL がその存在意義を発揮するのは、複数のクライアント種別が同じデータの異なる形を本当に必要としている場合です。この誕生の経緯が、グラフを好むというだけの理由ではなく、以下のすべての出発点となる判断基準です。
メモリ(推奨): メモリをファイルに保存する場合は、developer-platform-context.md の使用を検討してください。別のメモリシステムを使用している場合は、そちらを利用してください。このファイルは参考情報であり、必須ではありません。タスク情報はセクションを分けて記録してください。完了したタスクは削除してください。タスクには日付を追加し、プロジェクト全般のコンテキストには日付を付けないでください。タスクが2つある場合、インタビューの回答が異なることがあります。
確認事項
設計に着手する前に、以下を確認してください。それぞれの回答によって後続の手順が変わります。戦略面のインタビューではなく、実務的な設計タスクなので、まとめて質問してください。
- 新規構築ですか、それとも既存スキーマの改修ですか?改修の場合は、現在の SDL と、本番環境で実際に使われているクエリを5~10件提示してもらってください。
- 利用者は誰ですか。ファーストパーティアプリのみ、サードパーティ開発者のみ、それとも1つのエンドポイントを両者で利用しますか?(この区分は、次のセクションで説明する永続化クエリのポリシーとガードレールの予算を左右します。)
- パブリックな REST の表層も共存し、オブジェクトを両方から指定できる必要がありますか?(手順2の ID 戦略を左右します。)
- フェデレーションはすでに導入されていますか。それともスキーマの所有権がチーム間で分かれていますか?(手順6を左右します。)
- 既存のクライアントがすでにクエリしているフィールド、型名、エラー形式は何ですか?外部から観察できるものはすべて契約です(ハイラムの法則)。再設計では既存の識別子を新しいものへ引き継ぎ、黙って削除することはありません。
- リリース期限と作業量の上限はどの程度ですか。一度限りの表層ですか。それとも、今後何年にもわたって使うプラットフォームの主要 API ですか?(ページネーションとエラーパターンの選択肢の優先順位を決める際に影響します。手順3と4を参照してください。)
ハーネスに永続メモリがある場合は、設計で確定した決定事項を保存してください。そうすれば、後続の実行(新しい型の追加、スキーマレビュー、ドキュメント作成)が、設計を再導出せずにその内容から始められます。
- Mutation の命名方針。
- Null 許容性と ID 戦略。
- フィールドごとのページネーション段階。
- エラーの表層に関する方針。
- 深度とページサイズの上限。
- フェデレーションの境界。
- 永続化クエリのポリシー。
対象利用者の区分:ファーストパーティとサードパーティ
設計を左右するのは、ファーストパーティとサードパーティのどちらが利用するかという区分です。
- ファーストパーティクライアント(自社の Web アプリやモバイルアプリ)— あらかじめすべてのオペレーションがわかっているため、クエリを事前登録し、複雑度の予算を大きく設定し、破壊的変更を社内で調整できます。
- サードパーティ開発者 — これまで見たことがなく、事前承認もできないクエリを作成します。保守的なパブリック上限、十分に文書化されたスキーマ、進化に関する保証が必要であり、許可するオペレーションを限定するクローズドな許可リストは実現できません。
両者が利用する場合は、サードパーティ利用者を前提に設計してください。ファーストパーティのトラフィックには、ファーストパーティだからこそ可能な、より厳格なポリシーを適用します(手順7)。未知のサードパーティにも安全な表層なら、自社アプリもそのまま安全に利用できますが、その逆は成り立ちません。
ワークフロー
- 判定:この API は本当に GraphQL で提供するべきか?
- スキーマの規約を定める — 命名、型、Null 許容性、ID。
- ページネーションを設計する — Relay カーソル接続。
- エラーの表層を設計する — 裸のエラーではなく結果型を使用。
- ガードレールを設定する — 深度と複雑度の上限、イントロスペクション、リゾルバーのバッチ処理。
- フェデレーションの信頼境界を定める。
- 永続化クエリのポリシーを設定する。
- 進化の方針を引き継ぐ。
各手順の説明は、以下に順番に記載しています。
1. GraphQL を採用するかどうかのゲート
公開 API のデフォルトは GraphQL ではなく REST です。これは内部 API を設計するときの直感とは逆です。Postman の調査データでは、デプロイ済みの公開 API の 80%超が REST である一方、GraphQL をどこかで使用している組織はおよそ 28%です。チームがすでに採用を決めた状態で臨んだ場合でも、あらためて問い直してください。勢いで採用を決めることこそ、このゲートが防ごうとしている失敗です。
REST をデフォルトとする具体的な理由は 3 つあります。
- HTTP キャッシュは REST なら追加作業なしで利用できます(URL をキーとする GET は CDN やプロキシでキャッシュされます)が、GraphQL の単一 POST エンドポイントでは自前で構築する必要があります。
- 認証、レート制限、デバッグのエコシステムは、まず REST を前提としています。
- 固定され、一覧化できるエンドポイント群のほうが、使い方を習得して適切にたどる必要のあるグラフより、なじみのないサードパーティーにとって理解しやすいものです。
次のような、実際の採用条件に該当する利用者構成では、GraphQL への投資に価値があります。
- 複数の種類のクライアントが、同じデータに対して実質的に異なる形を必要としている場合(Web、モバイル、パートナーとのインテグレーションなど)。
- クライアントが、REST では何度も往復する必要が生じるか、専用エンドポイントが増え続けるような、ネストされた可変性の高いデータを必要としている場合。
- 往復回数が読み込み時間を大きく左右する、モバイルや高レイテンシの利用者がいる場合。
ここで効率性の順位付けはしません。これは採用条件のリストに照らして判断する二者択一のアーキテクチャゲートであり、選択肢の比較メニューではありません。答えが利用者構成だけで決まる場合に、2 つのパラダイムを労力あたりの価値で順位付けするのは、精度があるように見えて実際には不正確です。いずれにせよ、本番環境で主流なのはハイブリッドパターンです。安定してキャッシュ可能な領域には REST を使い、採用条件を満たす領域には GraphQL を使います。両方を提供することは、このゲートで決めきれなかったという失敗ではなく、妥当な結論です。
このゲートを終える道筋は 2 つあります。
- 採用条件に該当しない場合:ここで終了し、REST 側の関連タスクに引き継ぎます。
- 領域の選定に関する問いが GraphQL と REST の比較より広い場合:その全体的な判断には専用のスキルがあります(「参考資料」を参照)。
2. スキーマ規約
規約は一度定めて文書化し、それに対してリンターを実行します。公開スキーマは面識のない人にも読まれるため、どの選択をするかより、一貫性を保つことが重要です。
- 大文字・小文字の規則:型名は単数形の PascalCase、フィールドと引数は camelCase、列挙値は SCREAMING_SNAKE_CASE にします。真偽値には
is/has/canの接頭辞を付けます。 - ミューテーションは CRUD ではなく、業務上のアクションを表します。
publishPost、cancelOrderのように命名し、あらゆるフィールド変更を処理する単一の巨大なupdateOrderは避けます。文書化されている命名規則には、Apollo の動詞先行(createUser)と Shopify の名詞先行(型のミューテーションをアルファベット順にまとめるために採用されたuserCreate)の 2 つがあります。チームにどちらを採用するか確認し、その規則を全体で徹底します。 - すべてのミューテーションは専用の
input型を受け取り、専用のペイロード型を返します。出力型を引数として再利用してはいけません。両者では必要な null 許容性とフィールドが異なるためです。 - null 許容性は意図的に指定し、誤って決めないようにします。すべてのリストフィールドには
[Type!]!を使用します(null のリストより空のリストを優先)。非 null にするのは、ドメイン上で値が確実に存在する場合に限ります。ミューテーションのペイロードフィールドは、部分的な失敗でも何らかの値を返せるよう、null 許容にするのが基本です(Shopify のルール #24)。 - どのエンティティにも
Nodeインターフェース(interface Node { id: ID! })を実装し、すべてのオブジェクトをグローバルな不透明 ID で再取得できるようにします。REST のエンドポイントも併存する場合は、両方で共通の識別子を使用します。実例については GitHub のnode_idとの相互運用を参照してください(「参考資料」を参照)。 - 外部キーのフィールドではなく、オブジェクト参照を公開します。たとえば
authorId: ID!ではなくauthor: User!とします(Shopify のルール #8)。これにより、クライアントは 2 回目のラウンドトリップを行わず、1 回のクエリで必要な深さまで取得できます。 - フィールドの重複を避けるためだけに型を統合することは控えます。Marc-André Giroux の MOIST 原則(「一度の重複は許容できることがある」)によれば、
Viewer、UserProfile、TeamMemberはname/emailを共有していても、用途が異なり、それぞれ独立して進化します。型はデータベースのテーブルではなく、クライアントのユースケースを中心に設計します。 - 公開対象者向けに
"""docstrings"""を記述します。説明に内部システムの名前を含めると、スキーマを読むすべての人にアーキテクチャが漏れてしまいます。
大文字・小文字と null 許容性の全規則をまとめた表、アンチパターンの一覧、SDL の具体例については、references/schema-convention-tables.md を参照してください。
3. ページネーション
サードパーティが増やせるすべてのリストにはページネーションが必要です。また、単純なリストフィールドに後からページネーションを追加するには、互換性を損なう変更が必要です。フィールドごとに今の段階で決めてください。方式は3つあり、以下の順に評価できます。
-
効率性:
Relay cursor connections > plain cursor list > offset arguments -
導入の容易さ:
Relay cursor connections > plain cursor list > offset arguments -
価値:
Relay cursor connections > plain cursor list > offset arguments -
デフォルトの選択肢:Relay カーソル接続(
edges/node/cursor/pageInfo、first/after引数)。導入の手間が最も大きく、価値も最も高い方式です。ただし、クライアントライブラリやサーバーフレームワークがRelay仕様をネイティブに実装しているため、実際には導入の手間の大半が解消されます。そのため、効率性でも最上位になります。カーソルは並行書き込みがあっても安定し、どの深さでも高速です。オフセット方式では実現できません。 -
単純なカーソルリストに切り替える(
nodesとendCursorを使用し、edgeのラッパーは使わない)のは、edgeごとのメタデータが今後も一切不要で、チームが接続の定型コードを実際に大きな障害と見なす場合に限ります。カーソルのセマンティクスは維持できますが、仕様互換のツールは使えなくなります。GitHubとShopifyの公開GraphQL APIは、完全なedgesと並ぶ、サポート対象のショートカットとして、この形式をどちらもドキュメント化しています。そのため、明示的な公開事例のある一般的な慣行であり、暗黙の慣習ではありません。ただし、これはRelay仕様の任意フィールドであるnodesを利用するショートカットであり、対抗する標準ではありません。 -
オフセット引数(
limit/offset)が適するのは、ニッチなケースが1つだけです。小規模でほぼ静的なデータに対して、クライアントが任意のページへ直接移動する必要が本当にある場合です。並行書き込みがあると、オフセット方式では項目が重複したり、飛ばされたりします。また、深いオフセットでは全件スキャンが発生します。公開APIのデフォルトのリストにこの方式を採用するのは、選択肢ではなく失敗につながるパターンです。
3つの評価軸はいずれも同じ結論になるため、この順序なら何かを犠牲にすることはありません。最も導入の手間が大きい方式が最良の選択肢でもあります。これは、フレームワークのネイティブサポートによって導入の手間が解消されるためです。ただし、これはデフォルトであり、絶対的なルールではありません。質問6に照らして順位を見直してください。
ここで公開APIにオフセット方式を推奨することはありませんが、社内利用を優先するスキーマに厳しい期限がある場合は、段階的な移行策として単純なカーソル方式を選ぶ理由になります。後から完全な接続へ拡張しても追加的な変更で済むよう、最初からフィールドの戻り値をラッパーオブジェクトにしてください。
どの方式を選ぶ場合も、ページサイズはサーバー側で上限を設けてください(firstをドキュメント化された最大値、たとえば100に制限します)。スキーマでは上限を表現できないため、適用してドキュメント化する必要があります。接続のページサイズは、コスト予算でリストのコストを算出する際の乗数でもあります(ステップ5)。
完全な接続のSDL、カーソル設計のルール、totalCountのパフォーマンス上の落とし穴、型付きのソート/フィルター引数については、references/relay-pagination-examples.mdを参照してください。
4. エラーの扱い
GraphQL には、実質的に異なるエラー処理の仕組みが 2 つあり、どのエラーをどちらに振り分けるかが設計上の判断ポイントです。3 つの方針を順位付けすると、次のとおりです。
-
効率性:
ハイブリッドな分割 > すべての操作で結果型を使用 > 組み込みエラーのみ -
労力:
すべての操作で結果型を使用 > ハイブリッドな分割 > 組み込みエラーのみ -
価値:
すべての操作で結果型を使用 == ハイブリッドな分割 > 組み込みエラーのみ -
標準の段階:ハイブリッドな分割。 呼び出し側が対処を計画できないエラー、つまりサーバーの障害、不正なクエリ、認証エラーには、トップレベルの組み込み
errorsを使います。ミューテーションで想定されるビジネス上の結果には、すべてユニオン型またはインターフェースの結果型を使います。たとえば、union CreateOrderResult = CreateOrderSuccess | ValidationError | InsufficientInventoryのようにします。これにより、スキーマ自体が起こりうるすべての結果を文書化し、型付けされていないエラー本体の場合のように、クライアントが失敗ケースの処理を省略できなくなります。Shopify の設計チュートリアルも、組み込みのクエリレベルのエラーはビジネスレベルのミューテーション失敗には適さないと、同じ結論に独立して達しています。 -
クエリも含めてすべての操作で結果型を使用する方針に引き上げるのは、部分的なデータが日常的に発生する場合です。不安定なアップストリームを参照するフィールドごとに、独自の結果ユニオンを持たせます(
profileImage: ImageResult!)。これは最も負担の大きい段階です。あらゆる箇所で... on Typeフラグメントが必要となり、すべての利用者に最大の労力がかかります。また、ほとんどのスキーマではクエリに想定内の失敗が含まれることはまれなため、価値の評価でもハイブリッドな分割と並ぶにとどまります。日常的に部分的なデータが発生するなら、それ自体がこの方針へ引き上げる条件です。 -
組み込みエラーのみを許容できるのは、すべてのクライアントのエラー処理を管理できる、ファーストパーティ専用のインターフェースに限られます。サードパーティ向けのインターフェースではアンチパターンです。想定される結果が型付けされていない配列に隠れ、スキーマから見えなくなります。
-
上記の価値評価で同点となる点は、スキーマごとに検討してください。クエリに豊富な想定内の失敗が含まれるなら、同点は結果型を使う方に傾き、昇格の条件を満たしています。質問 6 に照らして順位を見直してください。複数のチームが何年にもわたって拡張するスキーマでは、リリース済みフィールドにユニオンを追加することは破壊的変更となるため、早い段階から結果型を使う方が適しています。
どの方針を選ぶ場合でも、その下に共有の ErrorCode 列挙型を設けます。これにより、メッセージ文面は自由に変更しながら、クライアントは安定したコードに基づいて分岐できます。これらのコードを支える、詳細な分類の徹底については、エラー設計に関する姉妹ドキュメントを参照してください(「参考資料」を参照)。
両方のパターンの完全な SDL、インターフェースのバリエーション、バッチ処理と部分的成功の形式、null とエラーによる情報開示の落とし穴については、references/error-result-type-examples.md を参照してください。
5. ガードレール
公開グラフでは、見知らぬ人が任意の形状のクエリを組み立てられます。ガードレールとは、スコアリングや使用量計測の仕組みが存在する前に、どのような形状を許容するかについて決めておく設計上の判断です。上限を設定するのはあなたの役割です。その上限のもとで使用量を計測するコストスコアリングの仕組み(ポイント計算式、期間ごとの予算、コストヘッダー)は、レート制限の関連項目に属します(「参考資料」を参照)。
- 深さの上限:ドキュメントに記載した最大値を超えるネストを含むクエリは、リゾルバーが実行される前に、パース時点で拒否します。Apollo のガイダンスに基づく開始時の目安は、シンプルな API で 5~7 レベル、複雑な API で 7~10 レベル、社内ツールで 10~15 レベルです。独立した実務者向けの情報源でも、おおむね同じ規模が示されています。公開 API 向けのセキュリティガイドでは一般に約 7 レベルが上限とされ、フレームワークのデフォルト(GraphQL-Ruby)では 15 レベルが設定されており、複雑なリレーショナルスキーマ(例:Autodesk の製造業向け API)では最大 20 レベルまで許容すると記載されています。公開エンドポイントは、デフォルトでは低いほうの上限を採用し、引き上げる場合はその都度、文書化された正当な理由を必要とします。
- 幅の上限:ステップ 3 のページサイズ上限に加え、エイリアス数、操作ごとのルートフィールド数、リクエストごとのバッチ操作数にも上限を設けます。深さだけでは、幅が広く浅いクエリによる呼び出しの拡散を防げません。Shopify のプラットフォームは各クエリのコストを実行前に静的解析し、予算を超えるクエリは処理を一切行わずに拒否します。完全なコストモデルがなくても、この事前実行時に上限をチェックするアドミッション制御の姿勢を取り入れ、コストモデルを導入した際にはミューテーションを読み取りより大幅に高く設定します(Shopify では 10 倍のコストが設定されています)。
- すべての上限を文書化する:深さ、ページサイズ、エイリアス、設定されている場合は操作ごとのコスト上限を、違反したクエリが受け取る正確なエラーとともに公開ドキュメントに記載します。文書化されていない上限は本番環境での障害を通じて発見されます。これはサードパーティの開発者にとって、最も望ましくないオンボーディング体験です。
- イントロスペクション:匿名の本番呼び出し元に対しては無効にし、開発環境と認証済みのツールに対しては有効にします。スキーマはエンドポイントのデフォルトとして扱うのではなく、意図的に公開するドキュメントとして扱います。
- リゾルバーのバッチ処理:解決時に親項目ごとに 1 回のルックアップを実行するリレーションシップフィールドは、すべて N+1 の不具合です。これを修正するには、リクエスト単位のバッチローダー(DataLoader パターン)を使用します。バッチ処理されていないリレーションシップリゾルバーは、ページネーション上限の欠如と同じ重大度の、必ず修正すべきレビュー指摘として扱います。スキーマの形状がリスクを生むため、その確認はスキーマレビューで担います。
- null とエラーの一貫した動作:権限のないフィールドは、対象のリソースが存在するかどうかにかかわらず、同じように動作しなければなりません。一方のケースでは null を返し、もう一方ではエラーになると、レスポンスの形状が存在確認の手掛かりになります。
6. フェデレーションの信頼境界
フェデレーションは内部構成のアーキテクチャであり、一般公開の仕組みではありません。スキーマがフェデレーションされている場合、外部から到達可能にするコンポーネントはルーターだけです。すべてのサブグラフはルーターの背後に置き、直接到達できないようにします。外部からのリクエストに応答するサブグラフがあれば、それはデプロイ上の都合ではなく、信頼境界の不備として扱ってください。
イントロスペクションを無効にしても、サブグラフを安全に公開できるわけではありません。フェデレーション独自の調整用フィールドから同じ情報が漏えいし、構成を壊さずにこれらのフィールドを無効にすることはできないためです。
Query._service { sdl }は、サブグラフの完全なスキーマを返します。別の名前で提供される、イントロスペクションの完全なダンプです。Query._entitiesは、@keyが付与されたエンティティを、そのキーのフィールドで直接解決します。サブグラフの通常のリゾルバーにあるアクセス制御ロジックを迂回し、リゾルバーは自動生成されるため、個別にロックダウンすることはできません。
Apollo は、その重要性を明確に述べています(2026 年):サブグラフの分離は「フェデレーションのセキュリティモデルの根幹」であり、AI を活用した検出ツールにより、誤って到達可能になったサブグラフが実際に発見される可能性はこれまで以上に高まっています。
境界そのものに加え、次の 2 つを設計のデフォルトとしてください。
- スキーマの所有権が、独立してリリースするチーム間で実際に分かれている場合にのみフェデレーションを採用します。小規模なパブリック API を担当するチームは、単一のスキーマを維持することで、設計上この問題全体を回避できます。
- 新しい型は標準の値型をデフォルトとし、別のサブグラフが本当に拡張を必要とする場合に限って、
@keyエンティティに昇格させます。
@inaccessible を使って、内部専用フィールドを合成後のパブリックスーパーグラフから除外します。境界を実際に検証してください。プライベートネットワークの外から各サブグラフのアドレスに接続し、GraphQL の応答ではなく接続失敗になることを確認します。
パートナーやサードパーティアプリがグラフにフィールド、データ、サブグラフを提供するケースは別の設計であり、独自の信頼契約が必要です。samber/developer-platform-skills@graphql-federation-offering を参照してください。
7. 永続クエリのポリシー
永続クエリでは、クエリ文字列全体を事前登録したハッシュに置き換えます。サーバーはそのハッシュを検索して実行します。設計上の判断は、誰にどのポリシーを適用するかです。判断の決め手となるのは、閉じた許可リストが機能するのは、クライアントとサーバーの両方を管理している場合に限られることです。graphql.org はこれを明言しています。信頼済みドキュメントは、「サードパーティクライアントから送信される操作が事前にわからないため、パブリック API では使用できません」。
そのため、このスキルの冒頭で示した対象者に沿って、ポリシーを分けます。
- ファーストパーティのトラフィック:閉じた許可リストを使用します。自社クライアントのコードベースからすべての操作をビルド時に抽出して登録し、それ以外はすべて拒否します。これはプラットフォームが実施できる最も強力な制御です。可能な限りどこでも適用してください。
- サードパーティのトラフィック:定義上、許可リストの適用は不可能です。包括的なポリシーとして導入しないでください。導入すると、API は初日から、正当なサードパーティクエリをすべて拒否します。サードパーティは、代わりにステップ 5 のガードレールで保護します。任意の機能として、パフォーマンス向上を目的とした永続クエリ(帯域幅の削減や CDN でのキャッシュ性向上のために、クライアントが独自のハッシュを登録する機能)を提供してもかまいません。ただし、セキュリティ制御として扱ってはいけません。
読み取りトラフィックが多い場合、キャッシュ性を高めるための設定は効果に見合う価値があります。ハッシュで識別される GET は REST の URL と同じように CDN でキャッシュでき、ステップ 1 で認めたキャッシュ上のギャップの一部を埋められます。
許可リストは多層防御の一部として扱い、決して完全な解決策とはみなさないでください。Apollo Router CVE-2025-32032 では、操作ごとのガードレールが適用される前に、再帰的なフラグメントによってクエリプランナーのリソースが枯渇しました。また、許可リストの回避は繰り返し発生する脆弱性の種類です。ステップ 5 の上限は、許可リストの適用有無にかかわらず、すべてのトラフィックに必須です。
8. 進化に引き継ぐ
GraphQLが掲げる理念は、バージョンを上げるのではなく、稼働中の単一スキーマを継続的に進化させることです。フィールド単位で@deprecatedを指定します。継続的な運用方針(非推奨化のワークフロー、利用状況のテレメトリー、削除の規律、通知期間)はバージョニングを扱う兄弟スキルの領域です。設計時のあなたの役割は、進化できるスキーマを残すことです。
- どこでも追加に強い形にします。ペイロードオブジェクトや結果のユニオンにはメンバーを追加できますが、スカラー型をそのまま返す場合はできません。
- 引き継ぎにはGirouxの注意点も添えます。非推奨化は「統合者に作業が発生するか、最悪の場合は破壊的変更となるため、軽々しく行うべきではありません」。仕組みの導入は簡単でも、統合者にとっては実質的なコストがかかります。
- GraphQLではバージョニングが禁止されていると考えないでください。ShopifyはGraphQL Admin APIを四半期ごとに日付ベースでバージョニングしており、新しい公開アプリの申請にはそれを必須としています。パラダイムはバージョニング方針を決定するものではありません。チームがどの方針を採るか記録し、バージョニングを扱う兄弟スキルに引き継いでください。
失敗パターン
アンチパターンのチェックリストです。それぞれが直接的なレビュー指摘になります。
- フェデレーションのサブグラフにプライベートネットワークの外部から到達できる(ステップ6を参照)。最も重大な指摘であり、このリストのほかの項目よりも優先されます。
- 永続クエリの許可リストを、サードパーティのトラフィックに包括的なポリシーとして適用している。
- 同時書き込みによって増加する公開リストフィールドに、オフセットページネーションを使っている。
- サードパーティ向けのインターフェースで、想定されるビジネス上の結果をトップレベルの
errors配列だけで報告している。 - 意図しないnull許容です。すべてをnull許容にして保証を隠すスキーマや、反射的に非nullにするフィールド(高コストの件数計算を強いる非nullの
totalCountや、部分的な失敗を表現できないペイロードフィールド)が該当します。 - オブジェクト参照ではなく、外部キーのIDフィールド(
authorId)を使っている。 - 公開フィールドにバッチ処理されないリレーションシップリゾルバー(N+1)がある。
- 匿名の本番環境の呼び出し元にイントロスペクションを公開している、または内部システムの名前を説明文字列に記載している。
- 上限(深さ、ページサイズ、コスト)が存在するのに文書化されていない、または文書化されているのに強制されていない。
- リソースの存在有無によってnullとエラーの挙動が異なる、つまり存在確認オラクルになっている。
- 目的に沿ったビジネスアクションごとのミューテーションではなく、単一のモノリシックなCRUD形式の更新ミューテーションを使っている。
測定
ゲートです。すべて合格するまで設計を反復してください。各項目は二者択一で判定します。
- ページネーション:増加しうる公開リストフィールドの100%が、文書化され、強制されるページサイズ上限付きのカーソル接続を使っていること。オフセットページネーションを使う増加可能なリストが0件であること。
- 信頼境界:プライベートネットワークの外部から到達できるサブグラフエンドポイントが0件であること。デプロイ設定を読むのではなく、各エンドポイントにプローブを行って確認します。
- 上限:すべての操作タイプに、文書化された深さの上限とページサイズ上限があること。違反するクエリが実行前に、文書化されたエラーとともに拒否されること。
- エラーの扱い:すべてのミューテーションが、想定されるビジネス上の失敗を含められるペイロード型または結果型を返すこと。ステップ4でファーストパーティ専用のインターフェースに対して組み込みのみの段階を明示的に選択した場合を除き、トップレベルの
errors配列だけを唯一の失敗通知手段とするミューテーションが0件であること。 - 規約:スキーマ全体で、命名規則とミューテーションの命名順序がそれぞれ統一されていること。スキーマを読む人にとって一貫性は二者択一の基準であるため、1つでも逸脱するフィールドがあれば不合格です。
これらの合格基準は、業界で公表されているベンチマークではなく、上記の根拠に基づく設計ルールからこのスキルが定めたものです。数値はチームの状況に応じて調整してください(方向性は変えないでください)。ローンチ後は、週ごとのガードレールによる拒否件数と、サードパーティが実際にどの上限に達しているかを確認してください。一度も到達されない上限は、高すぎる場合も低すぎる場合もありますが、その調整は運用の領域であり、設計ゲートではありません。
呼び出し例
- 「来四半期、GraphQL API をパートナーの開発者に公開します。リリース前にスキーマをレビューしてください。」
- 「注文プラットフォーム向けのパブリック GraphQL API を設計してください。モバイルチームはすでに内部でフェデレーションを利用しています。」
- 「サードパーティがこのスキーマにクエリを実行します。深度と複雑度の上限を設定し、永続化クエリが適切かどうか教えてください。」
- 「パブリック API は GraphQL と REST のどちらにすべきでしょうか? Web、モバイル、パートナーのクライアントが同じデータを利用します。」
期待される出力 — ワークフローの各ステップにつき 1 セクションの設計決定記録です。
- どのトリガーが該当したか、または該当しなかったかを明記したゲート判定。
- 命名規則一式:大文字・小文字の形式、ミューテーションの命名方式の分岐、null 許容性、ID 戦略。
- 各リストフィールドで選択したページネーションの段階と、ページサイズの上限。
- 生成される結果ユニオンを含む、エラーの表面化に関する方針。
- 各上限の文書化された値と、その上限を超えた場合のエラー。
- 境界を検証したプローブの結果を含む、フェデレーションの境界。
- トラフィッククラスごとの永続化クエリのポリシー。
既存のスキーマをレビューする場合も、同じセクションが所見として出力されます。その場合は、該当したアンチパターン、SDL 内の位置、違反している規約を記載します。
参考資料
- references/case-studies-github-shopify.md - GitHub の Node ID と REST の相互運用性および後方互換性を保つ ID 移行、Shopify のミューテーション規則、コストポイント、バージョン管理された GraphQL を解説します。
同じコレクションの関連資料:
samber/developer-platform-skills@api-integration-surface-strategy- REST、GraphQL、gRPC、SDK、Webhook を横断する包括的なサーフェス選択の判断を扱います。問いが GraphQL と REST の比較にとどまらない場合、ステップ 1 のゲートはこの資料に委ねます。samber/developer-platform-skills@public-api-design-review- サーフェス全体の一貫性をレビューします。GraphQL サーフェスのレビューはここに委ね、このスキルのチェックリストを GraphQL の評価基準として使用します。samber/developer-platform-skills@api-rate-limit-policy- ステップ 5 で設計する上限のもとでトラフィックを調整する、コストスコアリングと予算の仕組みを扱います。samber/developer-platform-skills@api-error-design- ステップ 4 のErrorCode列挙型の基盤となる、エラーコードの分類体系、エンベロープ、メッセージの規律を扱います。samber/developer-platform-skills@api-versioning-policy- ステップ 8 が引き継ぐ、継続的な進化、@deprecatedの方針、および通知期間を扱います。samber/developer-platform-skills@public-grpc-api-design- ステップ 1 のゲートがサービス間 RPC を指す場合に、gRPC 向けに同様のパブリックサーフェス設計を行います。