Skip to content

版本设计

本章导读:先说一个反直觉结论——理想的 REST 架构不需要 URI 版本号(Fielding 的立场:HATEOAS 可以让客户端"发现"变化)。但现实是绝大多数团队没有超媒体驱动,URI 一旦外发就改不动。所以本章讲工程上的版本策略:版本放哪、major/minor 怎么分、如何弃用而不炸客户端。

1. 什么时候需要版本

只有**破坏性变更(breaking change)**才需要升版本。先分清变更类型:

变更兼容性例子处理
向后兼容响应新增字段;新增可选查询参数;新增端点直接上线(客户端按"未知字段忽略"原则设计)
语义变更⚠️status=published 含义变化;默认值改变文档 + 新查询参数灰度,或升版本
破坏性变更删除/重命名字段;字段类型变化;URI 结构变化;校验变严升版本

治理要求: 团队规范必须写死"响应新增字段不算破坏性变更",且客户端禁止做"严格模式解析"(未知字段报错)——这是版本能少活一半的前提。

2. 版本号放哪里

方案优点缺点
URI 路径(主流推荐)/v1/articles/api/v1/articles可见、可路由、网关友好、日志可查、缓存天然分键URI 成为契约的一部分,迁移需重定向
查询参数/articles?api-version=1.0实现与测试简单CDN/网关忽略查询串时会串版本;不可路由
请求头Accept: application/vnd.example.api-v1+jsonURI 干净、贴近"内容协商"本质不可浏览、测试麻烦、代理无法路由
响应头声明服务端回 API-Version: 1.2仅作信息展示不能用于选择版本

实践建议: 对外 API 用 URI 路径版本(GitHub、Stripe、AWS 皆如此),理由不是"优雅"而是可运维性——网关按路径分流、监控按路径聚合、文档按路径生成。追求极致兼容的团队可"URI 主版本 + 头部次版本"组合。

3. 版本粒度:major 进 URI,minor 全局

/v1/articles        ← URI 只承载 major 版本
响应头: API-Version: 1.7.2   ← 可选:细粒度版本以头部/文档披露
  • major:不兼容变更的聚合包装窗(v1 → v2)。
  • minor/patch:向后兼容的功能迭代,不体现在 URI。
  • 语义化版本(semver)在 API 层的映射:MAJOR=破坏性、MINOR=加功能、PATCH=修 bug。

4. 多版本共存策略

                    ┌─────────────── gateway ───────────────┐
客户端 /v1/articles ─► 路由到 blog-svc:v1 部署单元            │
客户端 /v2/articles ─► 路由到 blog-svc:v2 部署单元            │
                    └───────────────────────────────────────┘
  1. 代码层多版本:同一服务内并存 v1/v2 控制器——适合小改动,慎用(逻辑分叉难维护)。
  2. 部署层多版本(推荐):网关按路径前缀路由到不同版本服务实例,服务内部只有"当前版本"。
  3. 反腐层:新版本服务内部适配旧模型,v1 → adapter → v2 core,只保留一份业务逻辑。

共存窗口 = 旧版本用户迁移期。目标:任何时刻并行版本 ≤ 2

5. 弃用与日落(Deprecation & Sunset)

废弃端点的标准动作(RFC 9745 Deprecation 头 + RFC 8594 Sunset 头):

http
GET /v1/articles/42

HTTP/1.1 200 OK
Deprecation: Thu, 31 Dec 2026 23:59:59 GMT
Sunset: Fri, 01 Jan 2027 00:00:00 GMT
Link: <https://docs.example.com/migrate-v2>; rel="deprecation"; type="text/html"
Link: </v2/articles/42>; rel="successor-version"

{ "id": 42, ... }

时间线模板:

t0        发布 v2,v1 进入维护(仅 bugfix)
t0+公告    文档/CHANGELOG/邮件通知;Deprecation/Sunset 头上线
t0+6mo    v1 请求开始记录调用方;对仍在使用的大客户定向支持
t0+12mo   Sunset 到期:v1 返回 410 Gone(或删除路由)——保留重定向更久

禁止:悄悄删除已发布端点、未通知收紧校验(如原来允许 200 字符现在 100)。

6. 无版本化(URI Versionless)路线

成熟团队终局常是"少版本":

  1. 只做加法(新增字段/端点/枚举值),永不原地改语义;
  2. 确需破坏时,新旧端点并存/articles/{id}/summaries 与旧的整页表示),靠链接/文档引导迁移,而不是全 API 升 major;
  3. 用 HATEOAS/Link 头让"可用操作"随状态变化(412/409 的 Link: rel="fixing-hypermedia"——IETF draft-dnotakis-restful-problem 思路)。

TIP

判断自己该不该上 URI 版本:调用方是否可控?(公司内部 API:可控 → 少版本、强治理;对外开放:不可控 → 版本化 + 弃用流程是生命线。)

7. 本章小结

  • 版本只为破坏性变更服务;治理"兼容变更自由通行"能大幅减少版本数量。
  • 对外 API:URI 放 major(/v1),细粒度版本进头部与文档。
  • 弃用要"有头有流程":Deprecation(RFC 9745)、Sunset(RFC 8594)、Link 指路迁移文档。
  • 并行版本 ≤ 2;到期返回 410,而不是静默失败。

8. 下一步

列表接口的"三件套":分页、过滤、排序 → 分页、过滤与排序