【入門編】 ヘッダーバージョニング(Accept: application/vnd.api.v1+json)の設計思想 – Web APIアーキテクチャ・データ連携実践ガイド

こんにちは!ネットワークとプロトコルの深淵を愛するインフラアーキテクトです。

日々のシステム開発やインフラ設計で、避けて通れないのが「APIのバージョン管理」ですよね。新しい機能を追加したり、データの形を少し変えたりしたいとき、古いシステムを使っているユーザーを壊さないように配慮するのは、本当に頭を悩ませる問題です。

APIのバージョンを伝える方法として、よく見かけるのは https://api.example.com/v1/users のようにURLの中に v1 を埋め込む方法(URLパスバージョニング)だと思います。一目で分かって直感的ですよね。

でも、今回はその一歩先。プロトコルの本質を突いた、ちょっと大人でスマートな手法である「ヘッダーバージョニング(Content-Negotiationを利用した手法)」について、身の回りの例えを交えながら、優しく紐解いていきたいと思います。

一歩ずつ理解していきましょう!

—

1. 郵便配達と「手紙の宛先・中身の指定」に例えてみよう

いきなり難しいHTTPの仕組みに入る前に、私たちの身近にある「手紙」を思い浮かべてみてください。

例えば、友達に手紙を送るときを想像してください。

  • URLバージョニングの世界は、宛先の住所自体を 東京都港区 v1町 1-1 から 東京都港区 v2町 1-1 にガラリと変えてしまうようなものです。郵便配達員(ルーターやサーバー)からすると、「あ、宛先の住所(URL)が変わったんだな」とすぐに分かります。
  • 一方、今回紹介するヘッダーバージョニングの世界は、宛先の住所は常に 東京都港区 中央町 1-1 で変えません。その代わり、封筒の表書きの隅っこに「中身は【2024年版の近況報告(v2)】です」と小さな付箋を貼るイメージです。

HTTPの世界では、この「付箋」の役割をするのが Accept というリクエストヘッダーになります。

—

2. ヘッダーバージョニングの具体的な姿を見てみよう

百聞は一見に如かず。実際にサーバーとクライアントが裏側でどんな会話をしているのか、HTTPのやり取りを覗いてみましょう。

クライアント(スマホアプリやブラウザ)がサーバーにデータを要求するとき、以下のようなリクエストを送ります。

GET /users/123 HTTP/1.1
Host: api.example.com
Accept: application/vnd.mycompany.v1+json

ここで注目してほしいのが、3行目の Accept ヘッダーです。ここに呪文のように書かれている application/vnd.mycompany.v1+json が、今回の主役です。

  • application/json: 「私はJSON形式のデータが欲しいです」という基本の意思表示です。
  • vnd.mycompany.v1: vnd は「Vendor(ベンダー、つまり自社)」の略。そのあとに続く自社名や、バージョンを表す v1 が組み込まれています。

これを受け取ったサーバーは、「なるほど、この人は v1 のデータ形式を求めているんだな」と理解し、それに合わせたデータを返します。もし将来 v2 が必要になったら、URLを変えずに、クライアント側が送る Accept ヘッダーを application/vnd.mycompany.v2+json に書き換えるだけで、新しいデータ構造を受け取ることができるのです。

URLがずっと変わらないため、APIの「見た目(エンドポイント)」が非常に美しく保たれるのが大きなメリットです。

—

3. Pythonを使った実際のコード例

「言葉だけだとなんとなく分かったけれど、実際にどう実装するの?」という方のために、Pythonの軽量WebフレームワークであるFlaskを使った簡単なサーバー側の実装例を見てみましょう。

インフラやバックエンドを担当するエンジニアにとって、コードの中でどうハンドリングされるかを知ることはとても大切です。

from flask import Flask, jsonify, request

app = Flask(__name__)

# ユーザー123のデータを表現するモック(模擬データ)
# v1では名前と年齢だけですが、v2ではメールアドレスが増えたという設定にします。

@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    # クライアントから送られてきた Accept ヘッダーの中身を取得します
    accept_header = request.headers.get('Accept', '')

    # バージョンごとのデータ構造を切り替える(Content-Negotiationの本体)
    if 'vnd.mycompany.v2+json' in accept_header:
        # v2用のリッチなデータ構造
        user_data = {
            "id": user_id,
            "name": "山田 太郎",
            "age": 28,
            "email": "yamada@example.com" # v2で追加されたフィールド
        }
        response_version = "v2"
    else:
        # デフォルト、または v1指定の場合のデータ構造
        user_data = {
            "id": user_id,
            "name": "山田 太郎",
            "age": 28
        }
        response_version = "v1"

    # レスポンスを構築
    response = jsonify(user_data)
    # デバッグ用に、今どのバージョンで応答したかをカスタムヘッダーで返してみる親切設計
    response.headers['X-API-Version'] = response_version
    
    return response

if __name__ == '__main__':
    # ローカル環境でサーバーを起動
    app.run(debug=True, port=5000)

このコードでは、クライアントが送ってくる Accept ヘッダーの文字をチェックし、v2 が含まれていれば新しい形式のJSONを返しています。URLは一貫して /users/123 のまま変わっていませんよね。

—

4. 美しい設計の裏にある「キャッシュの罠」(インフラエンジニアの視点)

「URLも変わらないし、スマートで最高じゃないか!」と思われたかもしれません。しかし、私たちインフラやネットワークのスペシャリストがこの手法を導入する際、最も頭を悩ませるのが「キャッシュ(Cache)の制御」です。

インターネットの世界には、サーバーの負担を減らして表示を高速化するために、途中のルーターやCDN(CloudflareやAWSのCloudFrontなど)といった「キャッシュサーバー」が存在します。

ここで、こんなトラブルを想像してみてください。

1. あるユーザーが Accept: application/vnd.mycompany.v1+json でリクエストを送り、CDNがそのレスポンス(v1のデータ)をキャッシュしました。
2. すぐあとに、別のユーザーが同じURL (/users/123) に対して、今度は Accept: application/vnd.mycompany.v2+json でリクエストを送りました。
3. CDNはこう考えます。「おっ、さっきと同じURLへのリクエストだな!じゃあ、さっきキャッシュしておいたデータをそのまま返しちゃえ!」

結果どうなるかというと、v2が欲しいと言っているのに、キャッシュされていた古いv1のデータが返されてしまうという悲劇が起きます。これが、ヘッダーバージョニングにおける最大の難所です。

解決のためのアプローチ

この問題を防ぐためには、キャッシュサーバーに対して「URLが同じでも、Accept ヘッダーの種類が違えば、別のデータとしてキャッシュしてね(区別してね)」と教えてあげる必要があります。

実務では、CDNやリバースプロキシ(Nginxなど)の設定で、「Varyヘッダー」を適切に利用します。

サーバーからクライアントへデータを返す際に、以下のようなヘッダーを付与します。

HTTP/1.1 200 OK
Content-Type: application/vnd.mycompany.v1+json
Vary: Accept
Cache-Control: public, max-age=3600

この Vary: Accept という一文が魔法の呪文です。「このキャッシュは、Accept ヘッダーの内容によって中身が変わるから、そこをちゃんと見てキャッシュを分けて保存してね!」とCDNやブラウザに指示を出すことができます。

—

まとめ:トレードオフを理解して使いこなそう

今回は、REST APIの美しいエンドポイント設計を支える「ヘッダーバージョニング」について、その設計思想とインフラ的な裏側の挙動を解説しました。

  • メリット: URLが汚汚しくならず、RESTの原則に忠実で美しいエンドポイントを維持できる。
  • 注意点: CDNやキャッシュの仕組み(Vary ヘッダーの適切な設定など)を理解していないと、古いデータが返る思わぬトラブルに繋がる。

技術に「絶対にこれが正解」という銀の弾丸はありません。URLパスで分かりやすくバージョンを分けるアプローチもあれば、今回のようなヘッダーでスマートに隠すアプローチもあります。

それぞれのメリット・デメリット、そしてパケットやキャッシュが裏側でどう動いているのかをイメージできるようになると、インフラやAPIの設計がもっと楽しく、深いものになりますよ。

それでは、また次回のネットワーク・プロトコル探訪でお会いしましょう!

コメント

タイトルとURLをコピーしました