API 契约版本控制策略

API 契约版本控制策略
Daniel Marsh · Spec-First 工程笔记

对比 URL 版本控制、请求头版本控制、基于日期的版本控制与 evolution-only。如何为 API 挑选合适的版本控制策略,以及规格文档必须就弃用时间线说清楚什么。

发布于 2026-02-08 · ✓ 已更新 2026-05-06 · 阅读约 7 分钟 · 作者:Daniel Marsh · 审校:编辑政策

现实中真正存在的四种策略

我交付过或审过的每一个 REST API,用的都是四种策略之一。URL 路径版本控制把版本放进路由里,所以 /v1/customers/v2/customers 是不同的资源。请求头版本控制让 URL 保持稳定,读取的是 Accept-version 或厂商自定义的媒体类型,比如 application/vnd.acme.v2+json。基于日期的版本控制是 Stripe 推起来的那一套——把客户端钉在某个日历日期上,比如 2023-10-01,把每一次破坏性变更都当成一次带日期的汇总更新。Evolution-only 则干脆拒绝做版本,永远只承诺兼容式增量演进。

除此之外都是方言。Query string 版本控制其实就是 URL 版本控制,只是缓存更糟糕。子域名版本控制会把你的 TLS 故事搞乱。要挑就挑得有意识,并把"为什么选它"写下来。

我为什么不再给 REST API 用 semver

我见过最常见的翻车,就是团队因为顺手就抓起 semver。Semver 是库和编译期消费它的用户之间的契约。线协议不是这么运作的。你升一个 minor,客户端不会被重新编译;他们会继续往你的端点上打请求,持续几个月甚至几年,直到自己决定迁移为止。

告诉大家"我们现在是 v2.4.1"传递不了任何可操作的信号。一个 minor bump 到底是加了字段、改了默认值,还是收紧了类型?不读 changelog 没人知道;而只要得读 changelog,版本号就只是装饰。我想要的是一个可以用来路由的整数,或者一个可以用来钉住的日期,而不是一个规则都说不清的三段式数字。

URL 版本控制:大多数团队的我的默认选择

对于一个面向产品、外部消费者不只是几个的 API,我默认选 URL 路径版本控制。每一个客户端工具、每一条 curl 命令、每一条代理日志,都能一眼看到版本。路由逻辑简单到不值一提,缓存不需要额外配置就能按路径做 key,新来的工程师扫一眼 access log 就能知道调用方期望什么。

坑是真的有。范围在 v1 的 auth token,如果权限模型变了,就不应该在 v2 上悄悄继续工作,所以 token introspection 必须知道版本这回事。钉在 /v1/* 上的 CDN 规则必须在 /v2/* 上再写一份。一旦 /v2 上线,所有新功能都只想落到 v2 上,于是 v1 开始腐烂——即便你承诺过支持 12 个月。要么提前预留回移关键修复的成本,要么就干脆别发 v2。

请求头版本控制:理论优雅,实践扎心

请求头版本控制在设计文档里看着很干净:URL 保持规范,版本是传输层的事,客户端自己协商要什么。实际上痛点来自"看不见的版本"。日志默认不打请求头。Curl 示例一粘出来就坏,因为没人记得加上 Accept-version 那一行。每一层缓存都得对这个请求头做 vary。一个配错的代理把它剥了,客户端就静默拿到了服务端的默认版本。

我只有在 URL 必须稳定是硬性要求时才会选请求头版本控制,这种情况通常是因为 API 是 hypermedia 驱动的。除此之外,人机工学上的税不值得交。

基于日期的版本控制:为什么 Stripe 是对的——对 Stripe 来说

Stripe 把每个账户钉在某个版本上,比如 2023-10-01。他们上一个破坏性变更时,就给它打一个新日期,你的账户会继续保持旧行为,直到你显式升级。对比一下 GitHub——他们用的是 URL 版本控制(/v3,后来 GraphQL 改成 /v4),而且极少宣布主版本升级。

这两种选择优化的是不同的东西。Stripe 在不停上破坏性变更,因为他们的领域本身在脚下移动;基于日期的版本控制让他们可以每个季度演进一次,而不必逼所有人按同一个节奏迁移。GitHub 优化的是一个巨大的第三方生态,在这里稳定好几年比这个季度能多发一次破坏性变更要值钱得多。如果你的 API 十年才变几次,URL 版本控制是老实人。如果一年要变好几次,那基于日期的版本控制能把大家从永无止境的迁移里解救出来。

代价是基础设施。你得有一层转换层,在所有支持的日期和当前内部模型之间互相翻译请求和响应。除非你准备把这一层当成永久的编制养起来,否则别选基于日期的版本控制。

Evolution-only:没人承认自己选了的那种策略

Evolution-only 意味着你白纸黑字承诺:API 只以向后兼容的方式改——新增可选字段、新增端点、新增枚举值(老客户端会忽略)。不能删,不能收紧,不能让语义漂移。大多数所谓"用 semver"的内部 API,实际上干的就是这事。

我喜欢在两种场景下用 evolution-only:一种是两端都归我管的内部 service-to-service API;另一种是公网 API 但规模小到根本不应该出现破坏性变更的情况。难的不是规则,是纪律。总会有人想改名某个字段,或者把可选字段改成必填,规格文档得给评审者明确的权力说"不"。

什么算破坏性变更

没有一个共享的定义,每一次变更都会变成一场谈判。我在用的清单:

破坏性:删字段或端点、收紧类型(integer 收到正整数、string 收到 enum)、把字段从可选改成必填、响应形状没变但语义变了、请求里加一个必填字段、改错误码、改默认值。

非破坏性:给请求加一个带合理默认值的可选字段、给响应加一个字段、加一个端点、规格已经要求客户端容忍未知值的前提下加一个新 enum 值、加一个可选响应头。

枚举值上那个"在……的前提下"正是团队翻车的地方。如果你从没告诉过客户端要忽略未知枚举值,那再加一个就是破坏性的——哪怕它看起来只是增量。把容忍规则在你需要它之前就写进规格里。

弃用时间线和 Sunset 请求头

我的默认值:公网 API 在宣布破坏性新版本后至少给 12 个月;共享发布节奏的内部跨团队 API 给 3 到 6 个月;支付、身份这类高风险集成给 18 到 24 个月。任何少于 3 个月的都不是弃用,而是强制迁移。公告里就要老实承认这一点。

宣布下线用 RFC 8594Sunset 请求头,出现在被弃用版本的每一个响应里,再配一个指向迁移文档的 Deprecation 请求头。会打响应头日志的客户端能在 deadline 咬人前好几个月就看到它。规格文档应该要求这些东西从 v2 上线那天就在,而不是 v1 下线前一周才加。

规格必须扛起的验收标准

- Given an API spec declaring URL-path versioning with v1 live
  When a change removes a field from the v1 response
  Then the review is rejected as a breaking change

- Given v2 has shipped and v1 is in its deprecation window
  When a client sends a request to /v1/resource
  Then the response includes a Sunset header with the retirement date
   And a Deprecation header pointing to the migration guide

版本控制规格必须明说的那些事

五件事,每次都得有。第一,选定的策略,外加一段话说明它为什么匹配这个 API 的变更频率和受众。第二,一份针对你自己领域调过的破坏性变更定义。第三,带数字的弃用时间线,不是形容词。第四,客户沟通方案:弃用在哪里宣布、谁负责宣布、客户端如何以编程方式发现当前状态(Sunset 请求头、/versions 端点、status page)。第五,回滚姿态:如果 v2 证明走不通,v1 能不能重新吃流量,那个决策的截止点在哪里。

在第一条 /v1 路由存在之前,先把这五条写下来。等客户接上之后再补版本控制政策,是我见过的团队为之付出过最昂贵的返工。

可复制产物:契约评审包

当工作涉及 API 行为、schema、事件、重试或消费者预期时使用。它会把兼容性和发布证据提前摊开。

API 契约评审包:API 契约版本控制策略

本次要做的决策:
- 确认契约变化是否兼容,消费者需要什么迁移动作,发布后如何观察风险。

责任人检查:
- 产品责任人:
- 工程责任人:
- QA 或运维评审:

范围边界:
- 本次包含:
- 本次不包含:
- 仍需确认的假设:

验收证据:
- 测试或 fixture:
- 日志、指标或截图:
- 人工复核步骤:

契约边界:没有兼容性分类、消费者影响、重试行为和回滚说明,不进入发布。

评审追问:
- 没参加需求会的人还会误解哪里?
- 哪个证据能证明这次改动足够安全,可以发布?

编辑复核记录

复核日期:2026-04-28。本次补充了可复用产物,按相关主题 Hub 检查了文章定位,并收紧下一步链接,让页面更像可操作参考,而不是孤立长文。

关键词:API versioning · URL versioning · header versioning · date-based versioning · deprecation timeline · Sunset header · RFC 8594 · breaking change

专题阅读路径

这篇文章归入 API 契约 主题。先读 Hub,再结合下面的清单、模板或工具落到具体项目里。

交互式生成规格
填写表单,生成完整的功能规格 Markdown——免费使用,无需注册。
试用规格生成器

编辑说明

最近复核:2026-04-28。编辑部检查了示例、内链和可复制评审片段,确保内容更适合真实项目使用。

本文面向软件交付团队,介绍API 契约版本控制策略。示例均为工程场景说明,不构成法律、税务或投资建议。