API バージョニング
API のバージョニングは、既存のクライアント連携を壊すことなく継続的に進化しようとするあらゆる Web サービスの ライフサイクルにおいて、重要な課題となります。ビジネス要件が 変化し、バグが修正され、新機能が追加されるにつれて、API は すべての利用者に同時更新を強いることなくイノベーションを可能にする形で進化しなければなりません。不適切な バージョニング戦略は、一見無害に見える変更が、強制的に更新できない すでに配布済みのモバイルアプリケーション、特定の API 契約に依存するパートナーシステム、または金融取引を 処理するビジネスクリティカルな連携を壊してしまうという、壊滅的なシナリオを招くおそれがあります。この問題は、複数の 相互依存する API が協調して進化しなければならないマイクロサービスアーキテクチャや、何千もの サードパーティ開発者がインフラ上にソリューションを構築している公開 API において、さらに複雑になります。本記事では、主要な バージョニング戦略——URI versioning、header versioning、content negotiation を含む——を取り上げ、各アプローチの利点、 欠点、適切なユースケースを分析するとともに、廃止ポリシー、 backward compatibility 戦略、および依存するエコシステムの安定性を損なうことなく API の持続可能な進化を可能にする 変更の伝達パターンを確立します。
なぜ API をバージョニングするのか
- 避けられない Breaking changes:データモデル、認証、振る舞いの変更
- 多様なクライアント:モバイルアプリ、Web アプリ、更新サイクルの異なるパートナー
- Backward compatibility:移行期間中も旧バージョンを動作させ続ける
- 安定した契約:連携者に対する予測可能性を確保する
- 計画的な廃止:旧バージョンを制御された形で sunset する
バージョニング戦略
1. URI Versioning(最も一般的)
# URI path 内のバージョン
GET /api/v1/users
GET /api/v2/users
# 利点:
- 非常に可視的かつ明示的
- 異なるバージョンのテストが容易
- Cache-friendly(異なる URL)
- proxies/gateways でのルーティングが簡単
# 欠点:
- リソースの重複(v1/users, v2/users)
- code duplication につながる可能性
- 同じリソースに対する URL の変更
2. Header Versioning
# Custom header
GET /api/users
API-Version: 2.0
# Accept header (vendor MIME type)
GET /api/users
Accept: application/vnd.myapi.v2+json
# 利点:
- URI がクリーンで一貫したまま保たれる
- より RESTful(同じリソース、異なる表現)
- リソース単位でバージョニングできる柔軟性
# 欠点:
- 可視性が低い(headers の検査が必要)
- 手動テストが難しくなる
- 複雑なキャッシュ(header によって変わる)
3. Query Parameter Versioning
# Query string
GET /api/users?version=2
GET /api/users?api-version=2.0
# 利点:
- 既存の requests に追加しやすい
- ベース URI を安定して保てる
- HTTP クライアントにとって簡単
# 欠点:
- query parameters を煩雑にしうる
- 意味的に弱い(バージョンはフィルタではない)
- routing/caching の問題
4. Content Negotiation
# Media type versioning
GET /api/users
Accept: application/vnd.company.user-v2+json
# Schema versioning
POST /api/users
Content-Type: application/vnd.company.user.v2+json
# 利点:
- より RESTful かつ HTTP-compliant
- request/response を個別にバージョニングできる
- resource type 単位の粒度
# 欠点:
- 実装の複雑さ
- HTTP content negotiation の理解が必要
- デバッグがより難しい
API 向けの Semantic Versioning
# MAJOR.MINOR.PATCH(API 向けに適応した Semver)
MAJOR:Breaking changes
- endpoints の削除
- response 構造の変更
- 認証の変更
- 例:v1.0.0 → v2.0.0
MINOR:Backward-compatible additions
- 新しい endpoints
- responses への新しいオプションフィールド
- 新しいオプションの query parameters
- 例:v2.0.0 → v2.1.0
PATCH:Bug fixes
- 契約変更を伴わない修正
- Performance improvements
- 例:v2.1.0 → v2.1.1
# コミュニケーション
GET /api/v2/info
{
"version": "2.3.1",
"deprecatedAt": "2025-06-01",
"sunsetAt": "2025-12-01"
}
Backward Compatibility
Backward-Compatible な変更
- [OK] 新しい endpoints の追加
- [OK] requests へのオプションフィールドの追加
- [OK] responses への新しいフィールドの追加(クライアントは無視すべき)
- [OK] required フィールドを optional にする
- [OK] 既存の enums に新しい値を追加する
- [OK] バリデーションを緩和する(より多くの inputs を受け入れる)
Breaking な変更(新しいバージョンが必要)
- [X] endpoints の削除または名称変更
- [X] responses 内のフィールドの削除または名称変更
- [X] データ型の変更(string → number)
- [X] requests への required フィールドの追加
- [X] バリデーションの厳格化(以前は受け入れていた inputs を拒否する)
- [X] 認証/認可の振る舞いの変更
Deprecation Policy
# 1. 廃止の告知(6〜12 か月前)
{
"data": [...],
"deprecated": true,
"deprecation": {
"date": "2025-01-01",
"sunset": "2025-07-01",
"alternativeVersion": "v3",
"migrationGuide": "https://docs.api.com/migrate-v2-to-v3"
}
}
# 2. 廃止 Headers
Deprecation: true
Sunset: Wed, 01 Jul 2025 00:00:00 GMT
Link: <https://docs.api.com/migrate>; rel="deprecation"
# 3. 利用状況の Monitoring
- バージョンごとに requests をログに記録
- deprecated なバージョンをまだ使用しているクライアントを特定
- 開発者への積極的な通知
# 4. Overlap 期間
v2 Launch ─────────────────────────────►
v3 Launch ─────────────►
v2 Deprecated ─────►
v2 Sunset
Express.js による実装
// Router-based versioning
const express = require('express');
const app = express();
// V1 routes
const v1Router = express.Router();
v1Router.get('/users', (req, res) => {
res.json({ version: 'v1', users: [...] });
});
app.use('/api/v1', v1Router);
// V2 routes
const v2Router = express.Router();
v2Router.get('/users', (req, res) => {
res.json({
version: 'v2',
users: [...],
metadata: { ... } // New in v2
});
});
app.use('/api/v2', v2Router);
// Header-based versioning
app.get('/api/users', (req, res) => {
const version = req.headers['api-version'] || '1';
if (version === '2') {
return res.json({ version: 'v2', users: [...] });
}
res.json({ version: 'v1', users: [...] });
});
GraphQL Versioning
# GraphQL は従来の versioning を必要としない
# schema evolution と @deprecated ディレクティブを使用する
type User {
id: ID!
name: String!
email: String!
username: String! @deprecated(reason: "Use 'name' field instead")
}
# Field-level deprecation
type Query {
users: [User!]!
getUsers: [User!]! @deprecated(reason: "Use 'users' query instead")
}
# 追加的な変更は本質的に backward-compatible である
# クライアントは自分が知っているフィールドのみを要求する
ベストプラクティス
- 1 つの戦略を選び、一貫性を保つ
- バージョニングポリシーを明確に文書化する
- 変更の影響を伝えるために semantic versioning を使用する
- 少なくとも 2 つのバージョンを同時にアクティブに保つ
- responses に deprecation warnings を実装する
- 詳細な migration guides を提供する
- バージョンごとに usage metrics を監視する
- cross-version テストを自動化する
- 変更を事前に伝える(changelog、メール)
- 迅速に更新できないクライアントを考慮する
最終的な推奨事項
公開され長期にわたる API では、URI versioning (/api/v1/) が その明確さと使いやすさから一般的に最良の選択肢です。変更の影響を伝えるために semantic versioning と組み合わせてください。マイクロサービスの内部 API では、 より高い柔軟性を得るために header versioning を検討してください。可能な限り常に backward compatibility を維持し、十分な 移行期間(最低 6〜12 か月)を伴う明確な廃止ポリシーを確立してください。
