目次を表示する

共通基盤の設計軸 2026 ─ 抽象・責務・非機能要件を設計する 15 章

API 設計の原則 ─ Boring API と進化容易性

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 の話。