API 設計の原則 ─ Boring API と進化容易性
責務と制御の所在が決まったら、最後に API の表面形 を決める。共通基盤の API は 数年〜十年単位で生きる ことが多い。最初の設計で間違えると、後から取り返しがつかない。
この章では Stripe を主役に、API 設計の 5 原則を整理する。
原則 1:Boring API ─ 退屈で信頼できる
第 2 章で導入した Boring API の感覚を、具体的に分解する。
良い Boring API の例
// 通知 API
POST /v1/notifications
{
"to": "[email protected]",
"subject": "Welcome",
"body": "..."
}
何が起きるか説明できる:
- POST してリソースを作る(HTTP 慣習)
- レスポンスは作成された通知の status
- エラーは standard な HTTP code(400 / 401 / 429 / 500)
退屈で予測可能。これが信頼の元になる。
❌ Sharp / “賢い” API の例
// 自動マジック
POST /v1/notify
{
"user": "[email protected]",
"event": "welcome",
"smart_template": true // ← user の言語 / 設定で自動切り替え
}
「smart」は短期的に楽だが、何が起きるか説明しづらい。デバッグが大変、テストしづらい、進化させにくい。
原則 2:Idempotency ─ 再試行可能性
ネットワーク越しの API は 必ず retry が起きる。同じ操作が複数回呼ばれても 結果が一回呼ばれたのと同じ になる必要がある。
Stripe の Idempotency Key
Stripe Engineering Blog “Designing robust APIs with idempotency”:
POST /v1/payment_intents HTTP/1.1
Idempotency-Key: 6a3a86b8-5e8c-4a9e-9e3d-1a2b3c4d5e6f
Content-Type: application/json
仕様:
- POST 系のみに付与(GET / DELETE は HTTP 仕様で idempotent)
- ヘッダーで client が UUIDv4 を渡す
- サーバーは status code + body を保存
- 同じキーで再 POST → 保存した結果をそのまま返す
- パラメータが違うと error
- 24 時間で expire
実装は前作 DB 設計の軸 2026 ch16 の 判断 1:Idempotency キー を参照。
Idempotency と GET の違い
// GET は仕様で idempotent
GET /v1/payments/123 // 何回呼んでも同じ結果
// POST は仕様では non-idempotent
POST /v1/payments // 1 回呼ぶたびに新しい payment が作られうる
// → Idempotency-Key で保護
POST だが「作成が成功したかどうかを問い合わせる」用途には、POST /v1/payments + Idempotency-Key + 同じキーで 24 時間以内に再 POST、という形が標準。
原則 3:エラー設計 ─ 意味のある失敗
❌ 1 種類のエラー
HTTP/1.1 500 Internal Server Error
{
"error": "Something went wrong"
}
利用者は retry すべきか / 設定を直すべきか / バグ報告すべきか 判断できない。
✅ 構造化されたエラー
HTTP/1.1 429 Too Many Requests
{
"error": {
"type": "rate_limit_exceeded",
"code": "TOO_MANY_REQUESTS",
"message": "Rate limit (100/min) exceeded for tenant t-123",
"retry_after": 30,
"doc_url": "https://platform.example.com/docs/errors#rate_limit_exceeded"
}
}
要素:
- type: マシン可読、利用者がコードで分岐できる
- code: 安定した識別子(changelog で追跡可能)
- message: 人間可読、デバッグの手がかり
- retry_after: 操作可能な指示(429 / 503 で必須)
- doc_url: ドキュメントへの直接リンク
Stripe のエラー型
Stripe Engineering Blog “Advanced error handling” は エラータイプを 6 つに分類:
| Type | 意味 | 利用者の対応 |
|---|---|---|
api_error | 基盤側のバグ | retry、報告 |
card_error | 入力(カード)の問題 | ユーザーに返す |
idempotency_error | キー流用 | キーを変えて retry |
invalid_request_error | パラメータ問題 | 修正 |
rate_limit_error | レート制限 | wait + retry |
authentication_error | 認証問題 | 認証情報確認 |
何をすべきかが type で分かる設計。
原則 4:命名規則 ─ 一貫性と予測可能性
Resource path
GET /v1/notifications # 一覧
POST /v1/notifications # 作成
GET /v1/notifications/{id} # 個別取得
PATCH /v1/notifications/{id} # 部分更新
DELETE /v1/notifications/{id} # 削除
REST の慣習に従う。新しい概念を発明しない。
Field 命名
// ✅ snake_case(多くの API の慣習)
{
"user_id": "u_123",
"created_at": "2026-05-09T10:00:00Z",
"is_active": true
}
// ❌ 混在
{
"userId": "u_123",
"created_at": "2026-05-09T10:00:00Z",
"isActive": true
}
1 API 内で命名規則が混在しない。利用者が「これは snake か camel か」を考える認知負荷を発生させない。
ID prefix
// ✅ Stripe 流:prefix で型が分かる
{
"id": "ntn_abc123", // notification
"user_id": "u_xyz789", // user
"tenant_id": "t_def456" // tenant
}
ログを見たとき / デバッグしたとき、prefix で何の ID か即分かる。これは Stripe で広まった実用パターン。
原則 5:進化容易性 ─ 数年後の自分を救う
API は変わる。最初から変わる前提で設計する。
Versioning:日付ベース vs path ベース
# Path ベース(一般的)
POST /v1/notifications
POST /v2/notifications
# 日付ベース(Stripe 流)
POST /notifications
Stripe-Version: 2024-04-10
Stripe API Versioning の日付ベースは:
- クライアントの初回 API call で自動 pin
- 各 version は backward-incompatible だが increment は小さい
- Compatibility layer で内部はモダン format、レスポンス変換層で過去 version に変換
Brandur “Why Doesn’t Stripe Automatically Upgrade API Versions?” では「自動 forward upgrade ができる場合」も論じられている:endpoint の変更が新版でしか起きていない場合、古い利用者を新版に自動昇格できる。
後方互換性のルール
| 操作 | 互換性 |
|---|---|
| Optional field の追加 | OK(後方互換) |
| Required field の追加 | NG(破壊的) |
| Field の削除 | NG |
| 型変更 | NG |
| Enum 値の追加 | 注意(client が unknown を扱える前提) |
| Enum 値の削除 | NG |
| Endpoint の追加 | OK |
| Endpoint の削除 | 段階的 deprecate |
「足すのは OK、引くのは NG」 が原則。これを守れば数年単位で互換性が保てる。
Deprecation の作法
# 古い endpoint
GET /v1/old_endpoint
HTTP/1.1 200 OK
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Deprecation: Sat, 9 May 2026 00:00:00 GMT
Link: <https://platform.example.com/docs/migration>; rel="deprecation"
Sunset / Deprecation ヘッダーで予告期限を明示。最低 1 年は維持するのが業界慣習。
API 設計のチェックリスト
graph TB
Q1[退屈で予測可能な API か?] --> Q2[Idempotent か?]
Q2 --> Q3[エラーが構造化されているか?]
Q3 --> Q4[命名が一貫しているか?]
Q4 --> Q5[後方互換性が保たれるか?]
Q5 --> OK[公開可能]
Q1 -->|No| Fix[Sharp / Magic を減らす]
Q2 -->|No| Fix2[Idempotency-Key を導入]
Q3 -->|No| Fix3[エラー型を分類]
Q4 -->|No| Fix4[命名規則を統一]
Q5 -->|No| Fix5[互換性チェック]
style OK fill:#e1ffe1
style Fix fill:#ffe1e1
この章の要点
- Boring API:退屈で予測可能。Sharp / Magic は短期的に楽でも長期コスト
- Idempotency:POST に Idempotency-Key、24h TTL、status + body 保存
- エラー設計:type / code / message / retry_after / doc_url の構造化
- 命名規則:snake_case 統一、ID prefix、REST 慣習
- 進化容易性:日付ベース versioning、後方互換ルール、Sunset / Deprecation ヘッダー
次章への問いかけ
API は決まった。だが API は 動き続ける ことが要求される。
利用者の SLO の和を背負う立場で、可用性をどう設計するか。次章で 非機能要件 (1) 可用性 ── SLI / SLO / Error Budget の話。