URLを汚さず、進化を止める。「Acceptヘッダー」によるAPIバージョニングの美学
現場でAPI設計をしていると、一度は必ず突き当たる壁がある。「APIのバージョン管理、どうやる問題」だ。
api.example.com/v1/users というURLを叩き、数年後に v2 が出たら api.example.com/v2/users に移行する。これは確かに分かりやすい。だが、リソースの本質が「ユーザー」であることに変わりはないのに、URLという「名前」を変えることが、RESTの哲学として本当に正しいのだろうか?
今回は、RFC 7231が定義する「コンテントネゴシエーション(Content Negotiation)」を活用し、URLを固定したままスマートにバージョンを切り替える「ヘッダーバージョニング」の世界を深掘りしていこう。
—
なぜURLバージョニングは「負債」になり得るのか
URLに /v1/ を含める方式は、キャッシュサーバー(CDN)やプロキシにとって非常に都合が良い。しかし、それは「リソースの識別」というRESTの原則を少しばかり犠牲にしている。
一方、Accept ヘッダーによるバージョニングは、「同じリソースに対して、異なる表現(Representation)を要求する」という、HTTP本来の仕様に忠実なアプローチだ。
ヘッダーバージョニングの強み
- URLの正規化: リソース(ユーザー情報など)のURIは永続的であり、バージョンの変更で変わることがない。
- 疎結合な進化: クライアント側でヘッダーを差し替えるだけで、サーバー側のロジックを段階的に切り替えられる。
—
現場で使う Accept: application/vnd.api.v1+json の作法
この形式は、ベンダー固有のメディアタイプ(Vendor Tree)を利用した設計だ。IANAに登録された標準的な application/json ではなく、自社独自の拡張であることを明示する。
通信フロー(シーケンス)のリアル
1. クライアント: GET /users/123 を送信。ヘッダーに Accept: application/vnd.api.v1+json を付与。
2. サーバー(ロードバランサー/API Gateway): ヘッダーを解析し、バックエンドの適切なロジックへルーティング。
3. サーバー(アプリケーション): 要求されたバージョンに基づき、JSONの構造を変換してレスポンス。
4. クライアント: バージョンに応じたデータを受け取る。
—
実践:クライアントからのリクエスト例
実際に curl や Fetch API で実装する際は、以下のように記述する。
curlでのリクエスト例
# ヘッダーを明示的に指定して叩く
curl -X GET https://api.example.com/users/123 \
-H "Accept: application/vnd.api.v1+json" \
-H "Authorization: Bearer <token>"
JavaScript (Fetch API) での実装例
fetch('https://api.example.com/users/123', {
method: 'GET',
headers: {
// サーバーに「v1のJSON構造をくれ」と伝える
'Accept': 'application/vnd.api.v1+json',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data));
—
運用上の最大の敵:キャッシュとの戦い
ここがインフラ屋としての腕の見せ所だ。この方式を採用すると、CDN(CloudFrontやFastlyなど)のキャッシュ制御が複雑化する。
通常、CDNはURLだけでキャッシュキーを生成する。しかし、同じURLでも v1 と v2 では内容が異なる。この場合、キャッシュキーに Accept ヘッダーを含める設定(Vary: Accept)が不可欠だ。
Nginxでの設定例
もしあなたがNginxでAPIゲートウェイを組んでいるなら、以下のように Vary ヘッダーを適切に返すよう設定しなければならない。
location /users/ {
# クライアントに「Acceptヘッダーによって内容が変わるよ」と教える
add_header Vary "Accept";
proxy_pass http://api_backend;
}
注意: Vary: Accept を付与すると、ブラウザやCDNが「Acceptヘッダーの組み合わせごとにキャッシュを作成」するため、キャッシュ効率が低下する可能性がある。トラフィックが膨大な環境では、このトレードオフを慎重に見極める必要がある。
—
シニアエンジニアからの助言
最後に、現場で泣きを見ないためのTipsを一つ。
「Accept ヘッダーを忘れたクライアントをどう扱うか?」
RFCでは、Accept が指定されない場合は「サーバーが最も適切と判断する表現」を返すことが許されている。しかし、本番環境で「いきなりv2に切り替えたら古いクライアントが全滅した」という事故を防ぐため、デフォルトは常に v1 を返すなどの「保守的なフォールバック」を設計に組み込んでおくこと。
また、APIのドキュメントには必ず「このAPIはContent-Negotiationを利用している」と明記し、Accept ヘッダーのサンプルを大きく載せておくこと。これを怠ると、後輩エンジニアが「なぜかv2のフィールドが取れない!」と深夜2時に泣きついてくることになる(笑)。
RESTは単なる規約ではなく、分散システムの調和のための「作法」だ。URLというインターフェースを美しく保つために、ぜひこのヘッダーバージョニングの知見を役立ててほしい。
コメント