LabHub

ブログ

API Versioning & 進化戦略 完全ガイド 2025: Breaking Changes なしの API 進化、Deprecation、Sunset

한국어English日本語

TL;DR


1. API 進化の本質的な難しさ

1.1 公開 API の運命

"一度公開された API は永遠に生き続ける。" — Hyrum's Law

内部コードは自由に refactoring できます。外部 API は異なります:

1.2 変更の種類

変更影響互換性
新しい endpoint の追加なしOK
新しい response field の追加なしOK
optional field の追加なしOK
response field の削除BreakingNG
field の renameBreakingNG
field type の変更BreakingNG
必須 field の追加BreakingNG
error code の意味変更BreakingNG
デフォルト動作の変更BreakingNG

ルール: 追加は安全、削除/変更は危険

1.3 Hyrum's Law の残酷さ

"API に十分なユーザーがいれば、どの観測可能な動作も誰かが依存している。"

実際の事例:

結論: 「公式ドキュメントにないものは変更してよい」は間違いです。観測可能なすべての動作が API の一部です。


2. Versioning 戦略 4 つ

2.1 URL Path Versioning

GET /v1/users/123
GET /v2/users/123

メリット:

デメリット:

採用: GitHub (/v3/)、Twitter、Stripe (URL は /v1/ だが実際は header でバージョン指定)

2.2 Header Versioning

GET /users/123 HTTP/1.1
Stripe-Version: 2024-04-15

メリット:

デメリット:

採用: Stripe (最も有名)、Azure

2.3 Content Negotiation

GET /users/123 HTTP/1.1
Accept: application/vnd.example.user.v2+json

メリット:

デメリット:

採用: GitHub (オプショナル、Accept: application/vnd.github.v3+json)

2.4 Query Parameter

GET /users/123?version=2
GET /users/123?api_version=2024-04-15

メリット:

デメリット:

採用: 一部のシンプルな API

2.5 比較表

方式URL がきれいdebugcache採用
URL PathNGGitHub、Twitter
HeaderStripe、Azure
Content NegotiationGitHub (オプション)
Query Paramシンプル API

3. Stripe の天才的な日付ベース Versioning

3.1 コアアイデア

Stripe はバージョンを 日付 で表現します:

すべての変更は日付で識別されます。

3.2 クライアント利用

import stripe

# アカウントのデフォルトバージョンを使用
stripe.api_version = "2024-04-15"

# または request ごと
stripe.Charge.create(
    amount=2000,
    currency="usd",
    api_version="2023-10-16"  # 古い動作
)

3.3 Stripe の秘訣 — 変換レイヤー

server にはただ 1 つのコードベース (最新バージョン)。

各 request に対して:

  1. クライアントバージョンを確認
  2. request を最新バージョンに変換 (forward transform)
  3. 処理
  4. response をクライアントバージョンに変換 (backward transform)
Client (v2020)[変換] → 最新コード → [変換]Client (v2020)

効果:

3.4 変換の例

v2020 から v2024 への変更: response に description field 追加。

# 変換関数
def transform_to_v2020(response):
    if "description" in response:
        del response["description"]  # v2020 クライアントはこの field を知らない
    return response

v2020 から v2024 への変更: amount が整数からオブジェクトに変更。

def transform_to_v2020(response):
    if isinstance(response.get("amount"), dict):
        response["amount"] = response["amount"]["value"]
    return response

3.5 Stripe の changelog

各バージョン変更が正確にドキュメント化されます:

2024-04-15

  • Added description field to Charge object
  • amount field type changed from integer to AmountObject
  • Default currency is now derived from account settings

Migration guide: ...

このレベルの透明性が信頼の核心です。


4. GraphQL のバージョンレス進化

4.1 コア哲学

GraphQL は バージョンを使いません。代わりに field 単位で進化:

type User {
  id: ID!
  name: String!
  
  # Deprecated field
  email: String @deprecated(reason: "Use 'emailAddress' instead. Will be removed 2025-12-31")
  emailAddress: String!
}

4.2 クライアントが正確にリクエスト

query {
  user(id: "123") {
    id
    name
    emailAddress  # 新しい field のみリクエスト
  }
}

既存クライアントは email をリクエストし続ける → 動作。新しいクライアントは emailAddress を使用。

Over-fetching がない = 新しい field の追加が無料。

4.3 Deprecation のトラッキング

GraphQL server が使用統計を収集:

Apollo StudioHasura Cloud がこの機能を提供。

4.4 GraphQL の限界

採用: GitHub、Shopify、Twitter、Airbnb


5. Semantic Versioning と API

5.1 SemVer の基本

MAJOR.MINOR.PATCH
v1.2.3

5.2 ライブラリ vs API

ライブラリ: SemVer が自然。

Web API: 適用が難しい。

現実: Web API は通常 major version のみ表示 (/v1/v2)。

5.3 SemVer の限界

v2.0.0 が出るとすべてのユーザーが migration 必要。段階的な進化が難しい。

Stripe 方式 (日付ベース) または GraphQL 方式 (バージョンなし) がよりエレガント。


6. Deprecation と Sunset

6.1 Deprecation ステージ

  1. Announce: blog、email、changelog
  2. Mark in API: response header または field
  3. Monitor: 使用トラッキング
  4. Reminder: 利用クライアントに直接通知
  5. Sunset: 廃止 (HTTP 410 Gone)

6.2 Deprecation header

RFC 8594: HTTP Sunset header

HTTP/1.1 200 OK
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: Sat, 31 Dec 2024 23:59:59 GMT
Link: <https://api.example.com/docs/migration>; rel="deprecation"

意味:

6.3 Deprecation メッセージ (response body)

{
  "data": {...},
  "warnings": [
    {
      "code": "DEPRECATED_FIELD",
      "message": "Field 'email' is deprecated. Use 'emailAddress' instead.",
      "documentation_url": "https://api.example.com/docs/v2#email-deprecation",
      "sunset_date": "2025-12-31"
    }
  ]
}

6.4 ユーザーへの通知

技術的:

コミュニケーション:

Stripe: 使用中の deprecated API について 自動 email を送信。

6.5 Sunset ポリシーの例

ユーザーSunset 期間
無料ユーザー6 ヶ月
有料ユーザー1 年
エンタープライズ2 年

大きな企業ほど変更に時間が必要。


7. Breaking Changes 回避戦略

7.1 Additive Changes を優先

誤った変更:

- "user_email"
+ "email"

正しい変更:

+ "email"  // 新しい field を追加
  "user_email"  // 古い field を維持 (deprecated)

両方の field を一緒に返す。クライアントに migration する時間を与える。

7.2 新しい endpoint vs 既存の変更

Bad: 既存の /users の response 形式を変更。

Good: 新しい /v2/users endpoint、または /users?format=new

7.3 デフォルト値に注意

// v1
{ "page_size": 20 }  // デフォルト 20

// v2 — 50 に変更?
{ "page_size": 50 }  // Breaking! (pagination 動作が変わる)

デフォルト値の変更はしばしば breaking です。

7.4 Optional → Required は Breaking

- email: string?  // optional
+ email: string   // required

既存のクライアントが email を送らないと失敗。追加しないでください

7.5 Enum 値の追加は安全?

enum Status {
  ACTIVE,
  INACTIVE,
+ PENDING_REVIEW  // 新しい値を追加
}

微妙: クライアントが enum を switch で処理していると、新しい値の default ケースが必要。あれば安全、なければ subtle bug。

アドバイス: enum の追加は技術的には backward compatible だが、クライアントコードのレビューが必要。


8. 実際の API 進化事例

8.1 Stripe — 日付ベースのエレガンス

8.2 GitHub — REST から GraphQL へ

教訓: 既存 API はそのまま残し、新しい paradigm は別途スタート。

8.3 Twilio — Major version + 段階的 migration

8.4 Slack — 段階的 deprecation

8.5 AWS — ほぼ絶対に壊さない


9. ベストプラクティス チェックリスト

9.1 設計段階

9.2 変更時

9.3 Sunset 時


10. API 進化の未来

10.1 OpenAPI 3.1 + JSON Schema

schema による自動互換性検査:

10.2 AI ベースの migration

10.3 contract testing の標準化


クイズ

1. 最も一般的な Breaking Change は?

答え: response field の削除または rename です。クライアントがその field を使用中なら即座に壊れます。安全な代替案: 新しい field を追加し、既存の field を deprecated としてマーク (両方を返す)。一定期間後に削除。その他よくある breaking changes: field type の変更 (string → object)、必須 field の追加、デフォルト値の変更、enum 値の意味変更。

2. Stripe の日付ベース versioning の利点は?

答え: (1) 段階的 migration — クライアントが自分のペースで新しいバージョンを採用、(2) 単一のコードベース — server は最新バージョンのみ維持し、変換レイヤーで古いクライアントをサポート、(3) 明確な changelog — 各日付の変更事項が正確にドキュメント化、(4) テスト容易 — 特定の日付バージョンを明示的にテスト可能。デメリットは変換レイヤーの実装が複雑なこと。

3. GraphQL がバージョンを使わない理由は?

答え: GraphQL は クライアントが正確に必要な field のみをリクエスト するため、新しい field の追加が既存クライアントに影響しません — 彼らはその field をリクエストしないので。Field の削除は @deprecated でマークし、使用統計をトラッキングして安全に削除可能。結果: バージョンなしで永遠に進化する API。デメリットはすべての変更が互換とは限らないこと (type 変更、enum 値削除など)。

4. Sunset header の役割は?

答え: RFC 8594 標準 HTTP header で、「このリソースがいつ廃止されるか」を伝えます。Sunset: Sat, 31 Dec 2025 23:59:59 GMT。クライアントはこの header を見て、migration スケジュールを自動認識できます。Deprecation header と Link header (migration guide) と共に使用。自動化されたクライアントが廃止スケジュールに安全に対応できるようにします。

5. Hyrum's Law が API 設計に意味することは?

答え: 「API に十分なユーザーがいれば、どの観測可能な動作も誰かが依存している。」つまり、公式ドキュメントになくても変更してはいけません。response field の順序、error message の正確なテキスト、response time、ID の連番性などすべてが「API の一部」になります。結論: (1) 最初から慎重に設計、(2) 変更時はすべての観測可能な動作を考慮、(3) 意図しない動作は明示的に「この動作に依存しないでください」とドキュメント化。


参考資料

コメント

まだコメントはありません。

ログインするとコメントできます