LESSON 30分

ストーリー

高橋アーキテクト
APIは一度公開すると、変更が難しい。なぜだと思う?

高橋アーキテクトが質問した。

あなた
他のシステムやアプリが、そのAPIに依存しているからですか?
高橋アーキテクト
その通り。100のクライアントがAPIを使っている状態で、レスポンスの形式を変えたら、100のクライアントが壊れる可能性がある。だからバージョニングが必要になるんだ

なぜバージョニングが必要か

APIの変更がもたらすリスク

// v1のレスポンス
{
  "name": "田中太郎"  // 単一の名前フィールド
}

// v2で名前を姓・名に分離したい
{
  "firstName": "太郎",
  "lastName": "田中"
}

// 問題: v1を使っているクライアントは "name" を参照している
// → "name" がなくなると、クライアントが壊れる(破壊的変更)

変更の種類

変更の種類後方互換性
フィールド追加互換新しいフィールドの追加
オプショナルパラメータ追加互換新しいクエリパラメータ
フィールド削除非互換既存フィールドの削除
フィールド名変更非互換name → fullName
型の変更非互換integer → string
必須パラメータ追加非互換新しい必須フィールド

3つのバージョニング戦略

1. URIバージョニング

// URLパスにバージョンを含める(最も一般的)
GET /api/v1/users/123      // バージョン1
GET /api/v2/users/123      // バージョン2

// メリット:
// - 分かりやすい(URLを見ればバージョンが分かる)
// - ブラウザでテストしやすい
// - キャッシュが容易(URLが異なるため)
// - 多くのAPIで採用されている(GitHub, Stripe, Twitter等)

// デメリット:
// - URLが変わるため、すべてのクライアントの変更が必要
// - 同じリソースに複数のURLが存在する

2. ヘッダーバージョニング

// カスタムヘッダーでバージョンを指定
GET /api/users/123
Accept: application/vnd.taskflow.v2+json

// または独自ヘッダー
GET /api/users/123
X-API-Version: 2

// メリット:
// - URLがクリーン(バージョン情報を含まない)
// - コンテンツネゴシエーションの仕組みに沿っている

// デメリット:
// - ブラウザでテストしにくい
// - ドキュメントに明示しにくい
// - クライアント側でヘッダー設定が必要

3. クエリパラメータバージョニング

// クエリパラメータでバージョンを指定
GET /api/users/123?version=2
GET /api/users/123?v=2

// メリット:
// - URLベースで分かりやすい
// - デフォルトバージョンを設定しやすい

// デメリット:
// - クエリパラメータが増える
// - キャッシュキーが複雑になる
// - あまり一般的ではない

戦略の比較

戦略明確さテスト容易性URL変更採用例
URIありGitHub, Stripe, Twitter
ヘッダーなしAzure, GitHub (v3)
クエリなしGoogle, Amazon

実装パターン

URIバージョニングの実装

// Express でのルーティング
import { Router } from 'express';

// v1 のルーター
const v1Router = Router();
v1Router.get('/users/:id', async (req, res) => {
  const user = await userRepository.findById(req.params.id);
  // v1の形式で返す
  res.json({
    data: {
      id: user.id,
      name: `${user.lastName} ${user.firstName}`,  // 姓名結合
      email: user.email,
    }
  });
});

// v2 のルーター
const v2Router = Router();
v2Router.get('/users/:id', async (req, res) => {
  const user = await userRepository.findById(req.params.id);
  // v2の形式で返す
  res.json({
    data: {
      id: user.id,
      firstName: user.firstName,  // 姓名分離
      lastName: user.lastName,
      email: user.email,
      avatarUrl: user.avatarUrl,  // v2で追加
    }
  });
});

// マウント
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

ヘッダーバージョニングの実装

// ミドルウェアでバージョンを判定
function versionMiddleware(req: Request, res: Response, next: NextFunction) {
  const acceptHeader = req.headers.accept || '';
  const versionMatch = acceptHeader.match(/vnd\.taskflow\.v(\d+)/);

  req.apiVersion = versionMatch ? parseInt(versionMatch[1]) : 1; // デフォルトv1
  next();
}

app.use(versionMiddleware);

app.get('/api/users/:id', async (req, res) => {
  const user = await userRepository.findById(req.params.id);

  if (req.apiVersion === 2) {
    res.json({ data: { firstName: user.firstName, lastName: user.lastName } });
  } else {
    res.json({ data: { name: `${user.lastName} ${user.firstName}` } });
  }
});

どの戦略を選ぶべきか

公開API(サードパーティ向け)?
├── Yes → URIバージョニング(最も分かりやすい)
└── No → 内部API?
    ├── Yes → ヘッダーバージョニング or バージョニングなし
    └── GraphQL?
        └── Yes → バージョニングなし(@deprecated で段階的移行)

まとめ

戦略推奨場面特徴
URIバージョニング公開API最も分かりやすく、一般的
ヘッダーバージョニング内部APIURLがクリーン
クエリパラメータ特殊なケースデフォルトバージョンの設定が容易
バージョニングなしGraphQL@deprecated で段階的移行

チェックリスト

  • バージョニングが必要な理由(破壊的変更)を説明できる
  • 3つのバージョニング戦略の違いを理解した
  • 場面に応じた戦略の選択ができる
  • バージョニングの実装パターンを把握した

次のステップへ

バージョニング戦略を学びました。

次のセクションでは、後方互換性の維持について詳しく学びます。 バージョンを上げなくても済む設計テクニックを身につけましょう。


推定読了時間: 30分