3.1 API 版本控制:契约的修订号


3.1 API 版本控制:契约的修订号

本节摘要:契约必然要修订,版本控制回答"怎么改、改得大家都安全"。三种主流策略——URI 版本、Header 版本、查询参数版本——各有取舍;破坏性变更与兼容性变更要分开管理(前者升大版本,后者原地兼容)。本节用一个"图书字段演进"案例,带你分清该升版还是该兼容,并给出版本生命周期的处理建议。

一次没有版本号的发布事故

想象一个没有版本观念的接口:产品说"把 author 字段改成 authorName",开发照着改了,顺手压缩了状态码范围,一起发了上去。第二天,十几个对接方一起崩——有的客户端还在用 author,有的依赖旧状态码,没人提前接到通知。这场事故不是"改错了",而是"契约没有版本,改动就没有边界"。版本号存在的全部意义,就是给每一次改动划清一条线,让线的这一边(升级方)和那一边(旧客户端)各自安好。下面从改动为什么必然发生讲起。

学习目标

阅读完本节,你应当能够:

  1. 比较 URI、Header、查询参数三种版本策略的取舍。
  2. 区分破坏性变更与兼容性变更,并给出正确的处置路径。
  3. 在"何时必须升版本 vs 何时原地兼容"上做对判断。
  4. 规划版本的生命周期:引入、标记弃用、移除的三个阶段。

契约为什么必然要改

没有哪个业务方案第一次就对。加了字段、改了名字、拆了模块,接口就得跟着动。但契约一动,客户端全部跟着动——这就是"修订号"(版本)存在的意义:给变化划一个边界,让"我用的这一版"在变化中仍能稳如泰山。

问题不在"要不要版本"(要),而在"版本放哪、怎么算破坏、何时升版"。把这三问过一遍,版本体系才有骨架,而不是随缘贴个 /v2 完事。

二、三种版本的放置策略

把三个主流位置对比,各自的取舍立刻浮出水面:

二、三种版本的放置策略

图:三种版本策略对比

策略 放哪 优点 代价
URI 版本 /v1/users 缓存、日志、mock 直观;客户端一眼可见 把"版本"这件非资源的事塞进地籍;每次都要维护路径
Header 版本 Accept: application/vnd.myapp.v2+json 符合自描述思想,不动 URI 旧客户端与调试工具不友好;报错难排查
查询参数版本 /users?version=2 实现最简单 语义弱、易混入其他参数、缓存分级难做

URI 版本把版本号放最前(/v1/users),好处是缓存、日志、mock 都直观,坏处是把"版本"这件非资源的事塞进了资源地籍;Header 版本用媒体类型协商(Accept: application/vnd.myapp.v2+json)更符合 REST 的自描述思想,但旧客户端与调试工具往往不友好。两者没有绝对优劣,视团队与生态而定。实操上的主流是"对外都走 URI 版本",因为它最容易被人、缓存、网关一眼识别;只在纯内部服务间才考虑用 Header 协商省掉路径负担。

三、破坏性 vs 兼容性:两本不同的账本

版本讨论最容易失焦的是"哪类变化算破坏"。其实就一张成色表:

  • 兼容性变更:加一个可选字段、加一个新 URI、放宽一项约束。旧客户端照常工作。处置:原地兼容,不必升大版本。
  • 破坏性变更:删字段、改语义、改返回码范围、改 URI 结构。旧客户端会崩。处置:必须升大版本,或走协商。

判断能不能"原地兼容",就看"此刻在线上跑的旧客户端会不会被弄坏"。加字段不坏、删字段会坏、改名会坏、同一含义换成另一种状态码会坏——记住这条线,比记一条条规则有用。

举一个真实场景。图书接口要加"版次"字段:

  • 做法 A:GET /book/42 的返回里加一个 revision 字段。旧客户端忽略它,完美兼容,原地做即可
  • 做法 B:把 author: "小七" 改名为 authorName。所有读 author 的旧客户端崩,必须升版本或以别名兼容

"以别名兼容"这条可以再拆细一层,它常是破坏性变更的软着陆。上面做法 B 若实在不想升大版本,可以保留 author 作为旧字段别名、同时新增 authorName,让新客户端用新名、旧客户端继续读旧字段,双方各取所需,等旧字段调用量清零后再在下一次大版本里彻底删除。用别名兜一路,能大幅推迟"不得不升版"的时点——阶梯升级的成本,往往低于一次性硬切。但这不等于无限期留着,别名本身也要纳入生命周期管理,否则又堆出一排僵尸字段。

四、版本生命周期:引入、标记弃用、移除

成熟接口对版本有完整生命周期,防止"版本字典无限膨胀":

  1. 引入:发布新版本,保留旧版,文档标注"旧版将在 X 年后弃用"。
  2. 标记弃用:旧版响应带 Deprecation 头与合理替代提示,持续提醒迁移,但不强制。
  3. 移除:统计调用量降到阈值后,才安全下线旧版,留出软性过渡期。

用一组数字把生命周期走得具体。2024 年你发布 v2,同时给 v1 打上"v3 发布即弃用"的标记;两年后 v2 调用量占比到 90%,有团队开始对 v1 发 Deprecation 响应头,提醒尽早迁移;三年后 v1 调用量降到个位数,才执行下线,且保留错误页面的迁移指引。这套节奏的核心是:升版不靠拍板,靠"调用量统计 + 提醒期 + 冷静期"三步走,任何一步没走完都不硬切。没有生命周期管理的接口,最后都会堆成一排谁也不敢动的僵尸版本。

⚠️ 常见坑:在一个版本里既做兼容又偷偷做破坏性变更,客户端会被"悄悄改坏"。正确做法是把破坏性变更明确归入大版本,绝不混进兼容改动里。

💡 关键直觉:版本的真正定价是"破坏性变更的代价"。把变化分级(兼容/破坏),再决定放哪个抽屉(原地/升版),版本体系就不会失控。

本节要点回顾

  • 要点一:版本给契约变化划边界,问题在放哪、何时升、怎么管。
  • 要点二:URI 版本、Header 版本、查询参数版本三种策略各有取舍,对外多用 URI 版本。
  • 要点三:判断破坏性 vs 兼容性,看"旧客户端会不会被弄坏"。
  • 要点四:加字段、加 URI 是兼容性变更,原地做;删改字段语义是破坏性变更,升大版本。
  • 要点五:版本要经历引入、标记弃用、移除三个生命周期阶段。
  • 要点六:绝不把破坏性变更混进兼容改动里偷偷上线。

版本把"契约怎么安全修订"定了下来,下一节我们把"怎么查这份契约"做成统一语法——过滤、排序、分页。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U