api-design について
プロトコル、ページネーション、リトライ、バージョニングなど、サービス API の契約を設計するスキルです。システム設計者が曖昧な要件を具体化し、負荷時の挙動も含めて判断できます。
主な機能
- 01呼び出し形態に応じたプロトコル選定
- 02ページネーション方式の指針
- 03冪等な書き込みとリトライの契約
- 04エラーとバージョニングの方針
- 05負荷と容量の検討
活用シーン
- 01新規サービスのインターフェース定義
- 02変化するデータセットのページング選定
- 03書き込みリトライによる重複処理の防止
- 04互換性を保つ API の変更計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill api-designインストール後、Claude Code で/api-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/deep-dive.mdreferences/providers/aws.mdazure.mdgcp.mdgeneric.md詳細
API 設計
クライアントとサービス間の契約を定義します。リクエストとレスポンスの正確な形式、呼び出し側がデータをページごとに取得する方法、再試行した書き込みの安全性、エラーの報告方法、契約の進化方法を定めます。ここが曖昧だと、残りの図は推測になってしまいます(GUIDE #8)。リクエスト、レスポンス、アクセスするキーを書けるようになるまでは、「NoSQL ボックス」や「ユーザーサービス」を用意しても何も解決しません。
これが必要になる場面
サービスとデータストアが決まり、次に必要なのがインターフェースである場合です。クライアントが送る内容、受け取る内容、次のページを取得する方法、二重請求せずに支払いを再試行する方法、v2 のデプロイ後も v1 クライアントを動作させる方法を決めます。誰かが形式を示さずに「フィードを取得する」や「投稿を保存する」と言った時点で、ここに取り組みましょう。
これが不要な場面
要件と規模が固まる前には取り組まないでください。プロトコルの選択は好みではなく、読み書きの比率とレイテンシの目標によって決まります(→ requirements-scoping、back-of-the-envelope)。今風に聞こえるからといって、gRPC、GraphQL、ストリーミングを選ばないでください。制約の大半を満たす最も低コストな契約は、シンプルな REST/JSON エンドポイントです。必要のない高度なプロトコルを選ぶのは、YAGNI の危険信号です。内部のデータアクセスキーとパーティション設計は data-storage の領域です。このスキルでは、それらを反映した外部契約を設計します。
まず確認すること
- 呼び出しの形式 — リクエスト/レスポンス(CRUD)、双方向/リアルタイム、それとも1回のリクエストから複数の結果を返すストリーミングですか? これによってプロトコルが決まります。
- 読み書きの比率と結果セットのサイズ — ページネーションや、読み取りに専用の最適化パスが必要かどうかを左右します(→
back-of-the-envelope)。 - 再試行の安全性 — 書き込みを安全に繰り返せますか? 自然に冪等な操作(PUT/DELETE)と、そうでない操作(作成や請求を行う POST)を見極めます。
- クライアントの多様性と更新頻度 — アップグレードが遅く厳密なバージョニングが必要な一般公開のサードパーティか、同時にリリースできる自社アプリか?
- レイテンシとペイロードの予算 — モバイルや高レイテンシの接続では、コンパクトなバイナリ形式と少ないラウンドトリップが有利です。ブラウザーではキャッシュ可能な HTTP が有利です。
選択肢
プロトコル/スタイル(呼び出しの形式に応じて選択)
- HTTP/JSON による REST — 標準的な動詞を使ったリソースの CRUD です。一般公開向けで、キャッシュ可能かつブラウザーとの親和性が高い API のデフォルトとして使います。
- RPC/gRPC(HTTP/2、protobuf) — 型付きのメソッド呼び出し、コンパクトなバイナリ形式、ストリーミングに対応します。レイテンシとスキーマ契約が重要な内部サービス間の通信に使います。
- GraphQL — クライアントが1回のクエリで必要なフィールドを正確に指定します。同じグラフから異なる形式のデータを必要とするクライアントが多く、REST での過剰取得や取得不足が問題になる場合に使います。
- WebSocket/SSE(ストリーミング) — サーバーからクライアントへの永続的なプッシュです。サーバーから更新をプッシュする必要がある場合(チャット、プレゼンス、ライブフィード)に使います。下記のポーリングの段階を参照してください。
- Webhook — イベント発生時に、サーバーがクライアントの URL を呼び出します。非同期のサードパーティ向けイベント配信に使います。
サーバープッシュの段階(クライアントが最新データを必要とする場合)
- ポーリング → ロングポーリング → SSE → WebSocketの順に、プッシュの効率が上がり、接続コストも増加します。まずはポーリングから始め、数値(更新頻度、ファンアウト)によって必要になった場合にのみ段階を上げます。
ページネーション
- カーソル(キーセット) — 安定したソートキーを基にした不透明なトークンです。大規模または変化するデータセットでは、これをデフォルトにします。
- オフセット/リミット —
?offset=40&limit=20。小規模でほぼ静的な、ページを直接指定するリストに限って使います。
冪等性 — 安全でない書き込みでは、クライアントが Idempotency-Key を送信し、サーバーが再試行を重複排除します。このスキルではキーの契約を定義します(「インターフェースのスケッチ」を参照)。
バージョニング — URI(/v2/...)、ヘッダー(Accept: application/vnd.x.v2+json)、または後方互換性を保った追加方式/破壊的変更の禁止方式があります。追加方式を優先し、新しいバージョンは破壊的変更のために確保します。
トレードオフ
| 選択肢 | 解決すること | 悪化すること | 変更するタイミング |
|---|---|---|---|
| REST/JSON | 汎用的でキャッシュ可能、シンプルで、あらゆる場所でツールを利用可能 | 過剰取得/不足取得、グラフではリクエストが増える、型付けが弱い | フィールドの形状調整が負担になったら → GraphQL、内部レイテンシが問題なら → gRPC |
| gRPC/RPC | コンパクトで、型付けされ、高速で、ネイティブなストリーミングに対応 | ブラウザーにネイティブ対応していない、プロキシが必要、HTTP キャッシュからは不透明 | 公開クライアント/ブラウザークライアントで必要になったら → REST ゲートウェイ |
| GraphQL | 1 回の往復で済み、クライアントがフィールドを選択可能 | キャッシュ/レート制限が難しい、クエリのコストが高い(N+1)、サーバーが複雑 | 固定の形状が少数なら(REST のほうがシンプル)、またはクエリコストが無制限なら |
| WebSocket/SSE | 真のサーバープッシュ、低レイテンシの更新 | ステートフルな接続、スケーリング/ロードバランシングが難しい、再接続ロジックが必要 | 更新頻度が低いなら → ロングポーリング、一方向のみなら → SSE |
| カーソルページネーション | 挿入があっても安定、どの深さでもページごとに O(1) | 不透明なトークン、任意のページへのジャンプ不可、ソートキーが必要 | ユーザーが静的なデータセットの N ページ目へジャンプする必要があるなら → オフセット |
| オフセットページネーション | 簡単、任意のページへジャンプ可能 | 挿入時にずれ/重複が発生、深いオフセットではスキャンが発生して遅くなる | データセットが拡大または変更されるなら → カーソル |
| 冪等性キー | 安全な再試行、二重請求を防止 | サーバーでキーの保存と重複排除が必要、キーの TTL/スコープを定義する必要がある | 操作が本質的に冪等(PUT/DELETE)なら → 省略できる場合がある |
| URI バージョニング | 明示的で、キャッシュ/ログから確認でき、ルーティングが容易 | バージョンの乱立、クライアントが永続的に特定バージョンへ固定 | 変更が追加のみなら → 新しいバージョンは不要 |
高負荷時の動作
契約によって、クライアントがインシデントをどれほど増幅するかが決まります。
- 再試行の嵐。 タイムアウトした書き込みが冪等でないと再試行され、二重実行される可能性があります。また、クライアントが一斉に再試行すると、復旧中のサービスに殺到します。冪等性キーを使えば再試行を安全にできます。再試行のペースを調整するバックオフ/ジッターは
resilience-failureが担当します。適切に動作するクライアントが速度を落とせるよう、429とRetry-Afterを返します。 - 深いページネーション。 大きなオフセットを使うオフセットページネーションでは、ストアが行をスキャンして破棄する必要があります。一見軽量なエンドポイントでも、クローラーによって全テーブルスキャンが発生します。カーソルページネーションなら、すべてのページで処理量を O(ページサイズ) に保てます。
- 無制限のレスポンス。 デフォルトのページサイズや最大ペイロードが設定されていない場合、または GraphQL クエリが多数の処理へ波及する場合、1 回のリクエストでメモリ/CPU を使い果たします。契約でページサイズ、深さ、複雑度に上限を設定します。
- 接続の枯渇。 WebSocket/SSE はクライアントごとに接続を保持します。デプロイ後の再接続の嵐で、ファイル記述子やロードバランサーのスロットが枯渇する可能性があります。ジッターと接続数の上限を考慮して再接続を計画します。
- バージョニングによる破損。 共有するデータ形状に後方互換性のない変更を加えると、すべてのクライアントが一度に壊れます。これは最も大きな、自ら招く障害です。変更は追加のみとし、バージョンを介して非推奨化します。
監視項目: ステータスクラス別のエラー率(4xx 対 5xx)、エンドポイントごとの p99 レイテンシ、再試行/冪等性リプレイ率、ページ深度の分布、開いている接続数、バージョンごとのトラフィック(古いバージョンを廃止できる時期を把握するため)。
適用方法
- 入力を明確にします。 呼び出しの形式、読み取りと書き込みの比率、結果セットのサイズ、再試行の安全性、クライアントの多様性、レイテンシとペイロードの予算を確認します(最初に確認することを参照)。これらが分かるまでは、契約を定めません。
- トレードオフ表からプロトコルとページネーションを選びます。 既定ではREST/JSONを使い、入力が必要とする場合に限り、gRPC(内部のレイテンシと型付け)、GraphQL(多様なフィールド形式)、またはプッシュ層(サーバーからのプッシュが必須)へ切り替えます。ページネーションはカーソル方式を既定とし、オフセット方式は小規模で静的な、ページを直接指定するセットに限って使います。
- 重要な設定を決めます。 安全性に基づいて冪等な動詞を選び(GET/PUT/DELETEは安全に再試行でき、POSTはできません)、
Idempotency-Keyの契約とTTLを定義し、安定したエラーエンベロープを固定し、バージョニング方針を選びます(既定では加算的な変更)。 - 契約に負荷をかけてテストします。 既定および最大のページサイズ、GraphQLの深さ/複雑度、ペイロードサイズに上限を設けます。プッシュでは、ジッターを加えた再接続と接続数の上限を計画し、
429+Retry-Afterを返します。v1クライアントがv2のデプロイ後も動作することを確認します。 - 数値を使って規模を見積もります。 エンドポイントごとのリクエスト/秒、レスポンスサイズ、セッションあたりのページ数、更新頻度 × ファンアウトを見積もり、これらを使ってプロトコルとポーリング/プッシュの選択を確認します(→
back-of-the-envelope)。 - プロバイダーを選びます。 ユーザーがクラウドを指定しない限り、汎用レシピを使います。指定された場合は、そのマネージドゲートウェイに対応付けます(プロバイダーの選択を参照)。
- 回答でトレードオフを示します — ここで行った選択ごとに1行を設けた
Decision | Solves | Worsens | Change it when…の表を提示します(例:オプション、そのストア、障害時の方針)。最後の列には、具体的な切り替え条件(数値または観測可能なシグナル)を記載します。次に、最初に問題が起きる箇所を挙げます。
すべきこと/すべきでないこと
すべきこと
- REST/JSONとカーソルページネーションを既定とし、数値が必要とする場合に限り切り替えます。
- 再試行が構造上正しく行われるよう、安全性に基づいて動詞を選びます。
- 冪等でないすべての書き込みで
Idempotency-Keyを必須とし、そのTTLに上限を設けます。 - ページサイズ、クエリの深さ/複雑度、ペイロードに上限を設け、最悪時のコストを制限します。
- すべてのエンドポイントで、安定したエラーエンベロープ(code、message、request_id、retryable)を1つに統一します。
すべきでないこと
- モダンに聞こえるという理由でgRPC、GraphQL、ストリーミングを選ばないでください(YAGNIの危険信号です)。
- 増加または変化するセットにオフセットページネーションを使わないでください。大きなオフセットはストアをスキャンします。
- 新しいバージョンを設けずに、共有される形式へ非加算的な変更を加えてリリースしないでください。
- レスポンスを無制限にしないでください(既定のページサイズや最大ペイロードを必ず設定します)。
- ここで再試行/バックオフやシャーディングを説明し直さないでください。
resilience-failure/data-storageへのリンクを提示します。
重要な数値
選択の前に、エンドポイントごとのリクエスト/秒、平均および最大レスポンスサイズ、ページサイズ × セッションあたりのページ数(読み取り負荷を左右します)、更新頻度 × ファンアウト(ポーリングとプッシュの選択を左右します)を見積もります。判断を変える目安をいくつか挙げます。バイナリプロトコル(protobuf)は一般にJSONよりペイロードを数分の一に削減でき、送信前に圧縮すればさらに削減できます。いずれも、ラウンドトリップが支配的になるモバイル環境や高レイテンシのリンクで特に重要です。間隔TでN個のクライアントがポーリングすると、ほとんど空のレスポンスに対してN/Tリクエスト/秒が発生します。その値がプッシュ層の接続数予算に近づいたら、SSE/WebSocketに切り替えます。最悪時のレスポンスコストが上限を超えないよう、ページサイズ(およびGraphQLの深さ/複雑度)を制限します。また、重複排除テーブルが際限なく増えないよう、冪等性キーを保存する期間にも上限を設けます(例:24h)。基準となるレイテンシ/QPS/ペイロードの参考値はback-of-the-envelopeを参照してください。ここの表を再掲しないでください。
インターフェイスの概要
契約を具体化します。カーソルページネーションとエラーエンベロープを使う読み取りの例:
GET /v1/users/{id}/posts?limit=20&cursor=eyJ0cyI6MTciLCJpZCI6Ijk5In0
200 OK
{ "data": [ { "id": "p_881", "created_at": "...", "text": "..." } ],
"next_cursor": "eyJ0cyI6...", // null when no more pages
"has_more": true }
# error envelope — stable shape across every endpoint
4xx/5xx
{ "error": { "code": "rate_limited", "message": "…", "request_id": "req_…",
"retryable": true } }
冪等な書き込み(このスキルが定める契約):
POST /v1/payments
Idempotency-Key: 9f1c-… (client-generated UUID, unique per logical operation)
{ "amount": 4200, "currency": "usd", "source": "card_…" }
# Server: first request with a key → execute, store (key → response) for a TTL.
# Retry with same key → return the stored response, do NOT re-execute.
# Same key + different body → 422 (key reuse conflict).
アクセスパターンに合わせてレスポンス形式を設計します。カーソルには、ストアがページネーションに使うパーティションキー/ソートキーをエンコードします(PK/SK設計はdata-storageの担当です)。安全性に基づいて動詞を選びます。GETは安全、PUT/DELETEは冪等、POSTは冪等ではないため、再試行が構造上正しく行われます。
プロバイダーの選択
まずは上記の汎用レシピを使用してください。ユーザーがクラウドを指定した場合は、
references/providers/<provider>.md を読み、マネージドサービスの対応関係、
クォータ/制限、プロバイダー固有のトレードオフを確認してください。そのプロバイダーの
ファイルがない場合は、汎用レシピを回答としてください。
図
リクエスト/レスポンスの経路、カーソルページングのループ、または
冪等性のある再試行シーケンス(クライアント → ゲートウェイ → サービス → ストア、リプレイ
分岐を含む)を可視化するには、プラグイン内の architecture-diagram スキルを使用してください。ここに Mermaid を埋め込まないでください。
簡単な推論であれば、1 行の ASCII 図をインラインで示しても構いません。
関連する構成要素
data-storage— 依存します:リクエスト/レスポンスとカーソルの形式は、主キーとアクセスパターン(シャーディング/パーティショニングはそちらで扱います)を反映するため、併せて設計してください。resilience-failure— 組み合わせて使用します:再試行/バックオフ/ジッター、タイムアウト、レート制限を担当し、ここで扱う冪等性キーによって再試行を安全に行えます。consistency-coordination— 再試行された書き込みや同時書き込みによって不変条件が破られてはならない場合に、組み合わせて使用します。CAP/クォーラム理論はそちらで扱います。messaging-streaming— 契約が非同期イベントまたは Webhook の場合に、同期リクエスト/レスポンスの代替となります。system-design— ここにつながります:オーケストレーターはインターフェースのステップでここにルーティングします。
参考資料
references/deep-dive.md— プロトコルの仕組み(HTTP の動詞/ステータスの扱い、gRPC のストリーミングモード、GraphQL のクエリコスト制限)、カーソルトークンの構築、冪等性キーの完全な状態遷移、バージョニング/非推奨化の方針、エラー契約の設計を扱います。契約を詳細に設計する際に読んでください。references/providers/{generic,aws,azure,gcp}.md— API ゲートウェイ/マネージドエンドポイントの対応関係、判断を左右する制限、環境ごとの落とし穴を扱います。