Skip to content

✍️ 필사 모드: API設計完全ガイド — REST・OpenAPI・Versioning・Pagination・Idempotency・Webhooks 2025年版

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

プロローグ — 「良いAPIは愛される」

2026年4月、Stripeのドキュメントを開く。リクエスト・レスポンス・SDKサンプルが1ページに並び、エラーは一貫したコードと丁寧なメッセージ、バージョンは明確(Stripe-Version: 2024-12-18)、Rate limit・Idempotency・Webhook署名も標準化されている。

なぜStripeが「最高のAPI」と呼ばれるのか。一貫性、予測可能性、ドキュメント — これは運ではなく設計の結果だ。

パフォーマンスが技術的品質なら、API設計開発者というユーザーへのUXである。良いAPIは予測可能で、安定し、寛容で、制限・非推奨化を前もって知らせる透明性を持つ。


1部 — Richardson Maturity Model

Level 0: 単一エンドポイント + POST ですべて
Level 1: リソースごとのURL(まだverbが残る)
Level 2: HTTPメソッドを正しく使う
Level 3: HATEOAS(ハイパーメディア)

現実: ほとんどのAPIはLevel 2。HATEOASは理論的には優雅だが実装コストに見合わない。


2部 — REST API設計原則10

1. リソース指向URL

Good: /users/123/orders
Bad:  /getUserOrders?userId=123

2. 複数形

Good: /users, /orders, /products
Bad:  /user, /order(混在)

3. HTTPメソッドの意味を守る

Method意味Idempotent
GET読み取り(safe)Yes
POST作成No
PUT全置換Yes
PATCH部分更新設計次第
DELETE削除Yes

4. Status Codeを正しく

200 OK / 201 Created / 202 Accepted / 204 No Content
400 Bad Request / 401 Unauthorized / 403 Forbidden
404 / 409 Conflict / 422 Unprocessable / 429 Too Many Requests
500 / 502 / 503 / 504

よくあるアンチパターン: すべてのエラーを200で返しbodyに{ "error": ... }を入れる。絶対禁止

5. RFC 9457準拠のエラー形式

{
  "type": "https://example.com/errors/insufficient-funds",
  "title": "Insufficient funds",
  "status": 422,
  "detail": "Account balance $50 is less than required $100",
  "instance": "/accounts/12345/transactions/abc",
  "errors": [
    { "field": "amount", "message": "must be <= balance" }
  ],
  "requestId": "req_abc123"
}

RFC 9457(Problem Details)が2023年にRFC 7807を置き換えた。

6. レスポンス形状の一貫性

エンベロープ形式かbare objectか、どちらかに統一する。

7. 時刻はISO 8601 + UTC

{ "createdAt": "2026-04-15T12:34:56Z" }

8. 金額は整数(minor unit)

{ "amount": 10000, "currency": "JPY" }

floatは精度問題を生む。

9. Boolean命名

isActive, hasPermission, canEdit など意味が明確な名前に。

10. IDは接頭辞付き文字列

{ "id": "usr_01HX9G7..." }

接頭辞(usr_, ord_)はデバッグを助け、連番整数の公開は列挙攻撃を招く。


3部 — OpenAPI 3.1実践

基本構造

openapi: 3.1.0
info:
  title: Example API
  version: 1.0.0
servers:
  - url: https://api.example.com/v1
paths:
  /users:
    get:
      summary: List users
      operationId: listUsers
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        - name: cursor
          in: query
          schema: { type: string }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserList'
components:
  schemas:
    User:
      type: object
      required: [id, email]
      properties:
        id: { type: string, pattern: '^usr_' }
        email: { type: string, format: email }
        createdAt: { type: string, format: date-time }

ZodをSingle Source of Truthに

import { z } from 'zod';
import { generateSchema } from '@anatine/zod-openapi';

export const User = z.object({
  id: z.string().startsWith('usr_'),
  email: z.string().email(),
  createdAt: z.string().datetime(),
});

export type User = z.infer<typeof User>;
const openApiSchema = generateSchema(User);

2025年の主要ツール

ツール用途
Zod + @anatine/zod-openapiTS-first、スキーマ自動生成
Typebox型フレンドリーなスキーマ
Hono + zod-openapiルーター + OpenAPI自動
tRPC + trpc-openapitRPCからOpenAPI変換
FastAPI (Python)Pydantic + OpenAPI標準
SpectralLinting
Scalar, MintlifyDocs UI

Spec-first vs Code-first: 2025年はCode-first優勢。Specは派生成果物。


4部 — Versioning戦略

5つの方式

  1. URL path/v1/users, /v2/users。明快、キャッシュに優しい。
  2. Accept headerAccept: application/vnd.example.v1+json。URLはきれいだがデバッグが難しい。
  3. Custom headerX-API-Version: 2024-12-18。Stripe方式。
  4. Query parameter?api-version=2。RESTらしくない。
  5. Content negotiationAccept: application/json; version=2

2025年の推奨

公開API:  URL path (v1, v2) + 日付ヘッダー
内部API:  Headerベース、rolling update
モバイル: URL path、古いアプリも長期サポート

Stripeの日付バージョニング

すべての版を永久にサポートし、内部でversion translatorが橋渡しをする。教訓は、バージョニングは長期コミットメントということ。

Breaking vs Non-breaking

Non-breaking: フィールド・エンドポイント・オプションパラメータ・enum値の追加(クライアントがunknown値を処理できる前提)。

Breaking: フィールドの削除・改名、型変更、required追加、URL構造変更、エラーコード変更。


5部 — Pagination

1. Offset-based

GET /users?limit=20&offset=40

シンプルでUIに優しい。Deep offsetは遅く、同時insertで重複・抜けが発生。

2. Cursor-based

GET /users?limit=20&cursor=eyJpZCI6MTAwfQ==

{ "data": [...], "nextCursor": "eyJpZCI6MTIwfQ==", "hasMore": true }

Deep pagination高速、順序一貫。ランダムアクセス不可。

3. Keyset(Seek Method)

-- 遅いoffset
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 100000;
-- 高速なkeyset
SELECT * FROM users WHERE id > 100020 ORDER BY id LIMIT 20;

非常に高速、インデックスを活かす。ソート条件変更時に複雑化。

選択指針

1 2 3 ... 10UIOffset
無限スクロール・APICursor
内部バッチ、大テーブル → Keyset

6部 — Idempotency

ユーザーが「支払」ボタンを押してタイムアウト、クライアントが再試行 — サーバーは同じ要求を2回受け取り二重課金になる。解決策がIdempotency Key

Stripe方式

POST /charges HTTP/1.1
Idempotency-Key: f8a9b2c3-...-xyz
Content-Type: application/json

{ "amount": 10000, "currency": "JPY" }

サーバーロジック:

1. Idempotency-Keyをチェック
2. 存在する場合:
   - 同じbody    → 保存済みレスポンス返却
   - 異なるbody  → 409 Conflict
3. 存在しない場合: 新規処理 + 結果を保存(24時間TTL)

対象メソッド

  • GET/PUT/DELETE — 本来Idempotent
  • POST — 必須
  • PATCH — 設計次第

Node.js + Redis実装例

async function idempotentHandler(req, res) {
  const key = req.headers['idempotency-key'];
  if (!key) return res.status(400).json({ error: 'Idempotency-Key required' });

  const lock = await redis.set(`lock:${key}`, '1', 'NX', 'EX', 30);
  if (!lock) await sleep(500);

  const cached = await redis.get(`idempotency:${key}`);
  if (cached) {
    const { bodyHash, response } = JSON.parse(cached);
    if (bodyHash !== hash(req.body)) {
      return res.status(409).json({ error: 'Conflicting request' });
    }
    return res.status(response.status).json(response.body);
  }

  const result = await processPayment(req.body);
  await redis.set(
    `idempotency:${key}`,
    JSON.stringify({ bodyHash: hash(req.body), response: { status: 200, body: result } }),
    'EX', 86400
  );
  return res.json(result);
}

7部 — Rate Limiting

3つのアルゴリズム

  • Token Bucket — 一定速度でトークン補充、バケットサイズまでバースト許容。実装容易。
  • Leaky Bucket — キューが一定速度で流れ、バーストを平滑化。
  • Sliding Window — 直近N秒を正確にカウント(Redis sorted setで実装)。正確だが難しい。

レスポンスヘッダー

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1697050800

# または RFC 9466 (draft):
RateLimit-Limit: 100, 100;w=60
RateLimit-Remaining: 42
RateLimit-Reset: 58

429 + Retry-After

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{ "error": "Rate limit exceeded", "retryAfter": 60 }

Redis+Lua、Cloudflare Rate Limiting、Envoy Rate Limit、Kong/Tyk/Traefikなどで実装。


8部 — Webhook設計

7つの必須要素

  1. Signing — HMAC-SHA256をtimestamp + "." + bodyに計算しX-Example-Signatureで送信。送信元の確認と改竄防止。
function verifyWebhook(body: string, signature: string, secret: string): boolean {
  const [t, v1] = signature.split(',').map(s => s.split('=')[1]);
  const timestamp = parseInt(t);
  if (Math.abs(Date.now()/1000 - timestamp) > 300) return false;
  const expected = hmacSha256(secret, `${t}.${body}`);
  return timingSafeEqual(expected, v1);
}
  1. Timestamp — 5分以上前は拒否しreplay攻撃を防ぐ。
  2. Retry with Backoff — 1分 → 5分 → 15分 → 1時間 → 6時間 → 24時間、最大3日。
  3. Idempotency — Webhook IDで受信側が重複処理を防ぐ。
  4. Ordered vs Unordered — Stripeはunordered、受信側がAPIで最新状態を再確認。
  5. VersionAccept-VersionヘッダーまたはペイロードのapiVersion
  6. Testing/Replay — ダッシュボードから再送、Stripe CLI stripe listen、開発環境でも署名検証を無効化しない。

受信側チェックリスト

1. 署名検証を最初に
2. Timestamp確認(5分以内)
3. Event IDで重複排除
4. 200を即返却、重い処理はキューへ
5. 非同期処理(Bull, Celeryなど)
6. 失敗時は5xxで発信側に再送させる

9部 — AsyncAPIとイベント駆動

CloudEvents(CNCF標準)

{
  "specversion": "1.0",
  "type": "com.example.order.created",
  "source": "/orders",
  "id": "evt_abc",
  "time": "2026-04-15T12:00:00Z",
  "datacontenttype": "application/json",
  "data": { "orderId": 123, "amount": 10000 }
}

AsyncAPI 3.0

asyncapi: 3.0.0
info:
  title: Order Service Events
  version: 1.0.0
channels:
  orderCreated:
    address: orders.created
    messages:
      orderCreatedMessage:
        payload:
          type: object
          properties:
            orderId: { type: string }
            amount: { type: integer }

Webhook vs Queue vs Stream

方式特徴
WebhookHTTP POST pushStripe, GitHub
QueuePull, 1:1SQS, RabbitMQ
StreamPull, 1:N, 順序保持Kafka, Kinesis

外部統合 → Webhook、内部ジョブ → Queue、監査・分析 → Stream。


10部 — REST vs GraphQL vs gRPC vs tRPC

REST + OpenAPI: 公開API、キャッシュ、多様なクライアント
GraphQL:        複雑なデータグラフ、フィールド選択
gRPC:           内部マイクロサービス、高性能、多言語
tRPC:           TSモノレポフルスタック、型自動

TypeSpec(Microsoft)が台頭 — OpenAPIへコンパイルされる簡潔なIDL。


11部 — セキュリティと認証

方式特徴
API Key簡単、ローテーション困難
OAuth 2.0ユーザー委任、複雑
JWT無状態、失効困難
Session Cookie失効容易、CSRF注意
mTLSサービス間、強力

API Keyベストプラクティス

  • 接頭辞(sk_live_, pk_test_)で種別明示
  • 32文字以上ランダム
  • ハッシュ保存、比較はハッシュ対ハッシュ
  • 権限分離(read-only、admin)
  • ローテーション容易に(旧+新の重複期間)
  • Secret Scanner(GitHub)連携

CORS

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization, Idempotency-Key
Access-Control-Max-Age: 86400

Allow-Origin: * + credentials はブラウザが拒否。必ず明示的なoriginを。


12部 — DeprecationとSunset

Sunsetヘッダー(RFC 8594)

Sunset: Wed, 01 Jan 2027 00:00:00 GMT
Deprecation: true
Link: <https://docs.example.com/v2>; rel="successor-version"

ポリシー: 6ヶ月前からDeprecationヘッダー、3ヶ月前にメール通知、1ヶ月前に最終通知、sunset後は410 Gone。移行ガイド、使用量ダッシュボード、SDKからの警告も用意する。


13部 — 6ヶ月ロードマップ

  • 1ヶ月目: REST原則10、メソッド・ステータス・エラー形式
  • 2ヶ月目: OpenAPI 3.1 + Zod + zod-openapi
  • 3ヶ月目: Pagination 3方式、Cursorエンコード/デコード
  • 4ヶ月目: Idempotency + Rate LimitingをRedisで
  • 5ヶ月目: Webhookシステム — 署名、再送、ダッシュボード
  • 6ヶ月目: ドキュメント自動化(Scalar, Mintlify)、SDK生成

14部 — 12項目チェックリスト

  • OpenAPI 3.1の自動生成(code-first)
  • HTTPメソッド・ステータス一貫
  • エラー形式RFC 9457準拠
  • Idempotency-KeyをPOSTで必須化
  • Rate-limitヘッダー
  • 新規エンドポイントはCursor pagination
  • Webhook署名(HMAC-SHA256)
  • Webhook再送 + ダッシュボード
  • Versioningポリシー文書化
  • Sunset/Deprecationヘッダー
  • CORSは明示的origin
  • SDK自動生成

15部 — アンチパターン10

  1. すべてのエラーを200で返す
  2. URLにverb(/getUserById)
  3. Paginationなしのリスト
  4. Idempotencyなしの決済API
  5. エラー形式が不一致
  6. バージョン変更なしのbreaking change
  7. Webhook署名検証スキップ
  8. Rate Limitなしの公開API
  9. 連番整数IDの公開
  10. 通知なしのエンドポイント削除

まとめ — 「APIは契約である」

APIは一度公開すると変更が難しく、顧客コードが依存し、breaking changeはコストになる。だからこそ慎重に設計し、徹底的に文書化し、長く維持する

2025年の十戒:

  1. リソース指向・複数形URL
  2. HTTPメソッド・ステータスを正確に
  3. エラーはRFC 9457形式
  4. OpenAPI 3.1をSingle Source of Truthに
  5. 明確なバージョニングポリシー
  6. Cursor-based Pagination
  7. POSTにIdempotency-Key
  8. Rate Limit + 標準ヘッダー
  9. Webhook署名 + 再送 + ダッシュボード
  10. Deprecation通知 + Sunsetヘッダー

次回はSaaSアーキテクチャ完全ガイド — Multi-tenancy、Billing、Feature Flag、Audit Log、RBAC/ABAC、Usage Metering。APIを作ったなら、次はそれをSaaSとして売る方法だ。

현재 단락 (1/284)

2026年4月、Stripeのドキュメントを開く。リクエスト・レスポンス・SDKサンプルが1ページに並び、エラーは一貫したコードと丁寧なメッセージ、バージョンは明確(`Stripe-Version: ...

작성 글자: 0원문 글자: 9,307작성 단락: 0/284