API版本管理
API 契约的版本选择、兼容判断、迁移与废弃通知,区分版本标识和真实兼容性。
[!info] related notes
- 所属 MOC: 后端开发 MOC
- 契约: 接口规范, OpenAPI / Swagger, 契约测试与 CDC
- 演进边界: Schema Versioning, Protocol Versioning, 数据库迁移与发布兼容性
- 验证: nodejs-backend-api-integration-testing, API 认证与安全
API 版本管理
API 版本管理让客户端明确依赖哪个契约,并为不兼容变更提供迁移路径。写上 /v2 只是标识,不能自动保证旧客户端兼容。
先判断是不是破坏性变更
例如把 { "name": "John Doe" } 改成 { "firstName": "John", "lastName": "Doe" },会影响依赖 name 的客户端,不意味着所有客户端必然崩溃。
常见不兼容变化包括删除或重命名字段、改变类型或单位、收紧请求约束,以及改变错误码、分页、排序、授权和幂等语义。
新增可选响应字段通常较容易兼容,但严格解码器可能拒绝未知字段;新增 enum 值可能破坏穷尽分支。新增必填请求字段通常会影响旧客户端。“新增”或“修 bug”本身不能证明兼容,需检查消费者行为。
版本选择方式
| 方式 | 示例 | 主要取舍 |
|---|---|---|
| URL 路径 | /api/v1/users | 路由、日志与缓存区分直观,客户端 URL 随版本变化 |
| 查询参数 | /api/users?version=1 | 保留路径;需定义缺省版本并确保缓存键包含版本参数 |
| 自定义请求头 | API-Version: 1 | URL 不变;网关与缓存需按该头区分响应,浏览器跨源可能触发预检 |
| 媒体类型协商 | Accept: application/vnd.example.user.v1+json | 用 HTTP 内容协商表达响应表示;需正确解析媒体范围、参数和优先级 |
| 子域名 | v1.api.example.com | 可分开运维,但增加域名、证书与跨源配置成本 |
Accept 表示客户端接受什么响应;请求中的 Content-Type 描述发送的请求体。两者不能互换,也不应把 Media Type 与 Header 写成互不相干的版本机制。
响应随请求头变化时,应正确设置例如 Vary: Accept 或 Vary: API-Version,并核对网关实际缓存策略。地址栏不方便设置任意请求头,不代表浏览器的 Fetch 无法选择版本。
路由与默认值
/api/v1/users → v1 契约处理
/api/v2/users → v2 契约处理
不支持的版本 → 明确的错误响应
未指定版本 → 文档规定的固定默认值,或要求显式选择
不要默默把未知版本按 v1 解析,也不要把默认契约自动漂移到“最新版本”。用 Header 或查询参数选择版本时,需处理缺失、类型不符和不支持值,避免请求无响应。
API 主版本可与实现的 SemVer 发布号分开。采用日期版本或其他标识也可行;没有所有 API 都必须只用主版本号的规则。
历史 DailyUse 方案
旧记录推荐路径 /api/v1/users、/api/v1/sync/push、/api/v1/devices,以及 Express 的 v1/v2 router 分组、对未变化的 sync 路由复用实现。
这是设计建议,不是当前 DailyUse 仓库或服务的部署证据。原 api.dailyuse.app/v1、/v2、/v3 与 2025-12-31 停用日期也只是旧方案示例,不能推定真实域名、版本或停服安排。
复用路由前需验证权限、输入、输出、错误和副作用仍满足两个版本的契约;仅因为处理函数相同不等于兼容。
数据适配与发布
- 适配器应保留旧契约语义;用空格拆分全名不能可靠恢复姓和名,这种转换可能不可逆。
- 新旧 API 共用数据库时,还要维护读写兼容窗口。应用回滚不等于数据结构可回滚,见 database-schema-migration-release-compatibility。
- 同一 API 契约内灰度新实现,与随机把旧契约请求转到不兼容 v2 是两件事。旧客户端不能仅按流量比例被强制送入新格式。
- 并行支持几个版本、保留多久,要结合客户端升级周期、使用数据和维护成本决定,不固定要求“两版、六个月”。
废弃通知
RFC 9745 中 Deprecation 使用 Structured Field Date,不是布尔字符串 true。Sunset 使用 HTTP 日期,表示预期停止响应的时间;废弃本身不等于立即停服。
下面是历史日期的协议格式示例,不代表实际服务日程:
Deprecation: @1688169599
Sunset: Sun, 30 Jun 2024 23:59:59 GMT
Link: <https://api.example.com/migration>; rel="deprecation"; type="text/html"
客户端按字段标准解析,不用 Deprecation === 'true' 判断。需要浏览器跨源读取时,服务端还应通过 Access-Control-Expose-Headers 暴露相应头。迁移说明应明确范围、替代方式与停用日期;Sunset 不应早于废弃日期。
正式移除旧资源时可以按 API 约定返回 410 等明确结果,不能把所有未知路由都误报成“版本已停用”。
验证重点
- 用真实消费者样例检查旧客户端 + 新服务、新客户端 + 过渡服务。
- 测试字段、类型、未知 enum、错误、分页、权限与写入副作用,不只断言几个字段存在。
- 检查版本选择、缺省与不支持值,防止缓存把不同版本响应混用。
- 检查废弃头格式、跨源可见性和迁移文档。
- 客户端应用版本与 API 版本分开记录;需要比较应用 SemVer 时按版本规则解析,不能按字符串大小判断(如
1.10.0与1.9.0)。