关于 api-versioning
一项 API 版本设计技能,介绍 URL 路径版本、端点分组和版本弃用,适用于规划新 API 或在保留现有版本的同时处理破坏性变更。
主要功能
- 01使用 URL 路径段标记 API 版本
- 02按 API 版本分组端点
- 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