api-versioning について
APIのバージョン設計を扱うスキルです。URLセグメント、エンドポイントのグループ化、バージョンの非推奨化を案内し、既存バージョンを維持しながら新規APIや破壊的変更に対応する開発者を対象とします。
主な機能
- 01URLセグメントによるAPIバージョン指定
- 02APIバージョン別のエンドポイント集約
- 03バージョンの非推奨化と状態通知
- 04破壊的変更と互換性のある変更の区別
活用シーン
- 01公開APIに初期段階からバージョン付きルートを設定
- 02レスポンス形式の破壊的変更を管理
- 03旧APIバージョンを非推奨化
- 04関連エンドポイントのバージョンを統一
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ codewithmukesh/ dotnet-claude-kit --skill api-versioningインストール後、Claude Code で/api-versioningを入力して読み込まれたか確認します。
詳細
API のバージョニング
基本原則
- 初日からバージョンを設定する — 後からバージョニングを追加するのは大変です。v1 しかなくても、最初から URL にバージョンを含めましょう。
- URL セグメントによるバージョニングを標準にする —
/api/v1/ordersは、最も見つけやすく、キャッシュにも適した方法です。 - 既存のバージョンを破壊しない — 破壊的変更には新しいバージョンを追加します。移行期間を設けて古いバージョンを非推奨にします。
- 個々のエンドポイントではなく、API をバージョニングする — 同じバージョングループ内のすべてのエンドポイントで、同じバージョン番号を共有します。
パターン
Asp.Versioning を使ったセットアップ
// Program.cs
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
URL セグメントによるバージョニング(推奨)
var v1 = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(1, 0))
.Build();
var v2 = app.NewApiVersionSet()
.HasApiVersion(new ApiVersion(2, 0))
.Build();
app.MapGroup("/api/v{version:apiVersion}/orders")
.WithApiVersionSet(v1)
.WithTags("Orders")
.MapOrderEndpointsV1();
app.MapGroup("/api/v{version:apiVersion}/orders")
.WithApiVersionSet(v2)
.WithTags("Orders")
.MapOrderEndpointsV2();
ヘッダーによるバージョニング(代替案)
options.ApiVersionReader = new HeaderApiVersionReader("X-Api-Version");
// Client sends: X-Api-Version: 2.0
バージョンの非推奨化
var v1 = app.NewApiVersionSet()
.HasDeprecatedApiVersion(new ApiVersion(1, 0))
.HasApiVersion(new ApiVersion(2, 0))
.Build();
// Response headers will include: api-deprecated-versions: 1.0
バージョン固有のエンドポイントグループ
public static class OrderEndpointsV1
{
public static RouteGroupBuilder MapOrderEndpointsV1(this RouteGroupBuilder group)
{
group.MapGet("/{id:guid}", GetOrderV1);
group.MapPost("/", CreateOrderV1);
return group;
}
private static async Task<Results<Ok<OrderResponseV1>, NotFound>> GetOrderV1(
Guid id, ISender sender, CancellationToken ct)
{
// V1 response shape
var result = await sender.Send(new GetOrder.Query(id), ct);
return result.IsSuccess
? TypedResults.Ok(result.Value.ToV1())
: TypedResults.NotFound();
}
}
public static class OrderEndpointsV2
{
public static RouteGroupBuilder MapOrderEndpointsV2(this RouteGroupBuilder group)
{
group.MapGet("/{id:guid}", GetOrderV2);
group.MapPost("/", CreateOrderV2);
return group;
}
private static async Task<Results<Ok<OrderResponseV2>, NotFound>> GetOrderV2(
Guid id, ISender sender, CancellationToken ct)
{
// V2 response shape — includes new fields
var result = await sender.Send(new GetOrder.Query(id), ct);
return result.IsSuccess
? TypedResults.Ok(result.Value.ToV2())
: TypedResults.NotFound();
}
}
アンチパターン
個々のエンドポイントをバージョニングしない
// BAD — inconsistent versioning within a group
app.MapGet("/api/v1/orders", ListOrdersV1);
app.MapGet("/api/v2/orders/{id}", GetOrderV2); // V2 only for this endpoint?
// GOOD — version the entire group
app.MapGroup("/api/v1/orders").MapOrderEndpointsV1();
app.MapGroup("/api/v2/orders").MapOrderEndpointsV2();
クエリ文字列によるバージョニングを標準にしない
// BAD for REST APIs — version hidden in query string, not cache-friendly
GET /api/orders?api-version=2.0
// GOOD — version in URL, discoverable and cacheable
GET /api/v2/orders
判断ガイド
| シナリオ | 推奨事項 |
|---|---|
| 新しい公開 API | 初日から URL セグメントによるバージョニングを使用する |
| サービス間の内部 API | ヘッダーによるバージョニング(URL がすっきりします) |
| レスポンス形式の破壊的変更 | 新しいバージョン |
| 新しい任意フィールドの追加 | 同じバージョン(後方互換性あり) |
| バージョンの非推奨化 | 非推奨としてマークし、提供終了日を設定して、移行方法を文書化する |
コメント
Markdown 対応