第4章:数据契约的版本管理
数据契约不是写一次就完事的。业务在变,数据在变,契约也得跟着变。但怎么变、变的时候怎么通知别人、旧版本怎么处理——这些搞不清楚,契约就成了新的麻烦。
我见过不少团队,一开始热情高涨,把契约写得漂漂亮亮。三个月后,字段改了,没人通知,下游直接挂掉。嗯,版本管理不是锦上添花,是保命用的。
4.1 语义化版本:给契约一个明确的“身份证”
数据契约的版本号,我建议直接用语义化版本规范(SemVer)。格式很简单:MAJOR.MINOR.PATCH。
| 版本位 | 变更类型 | 示例 |
|---|---|---|
| MAJOR | 不兼容的变更 | 删除字段、修改字段类型、改变枚举值含义 |
| MINOR | 向后兼容的新增 | 新增可选字段、新增枚举值 |
| PATCH | 向后兼容的修复 | 修正文档描述、调整字段顺序(不影响解析) |
核心原则:版本号一旦发布,就不能修改。要改就发新版本。
举个例子。你有个用户信息契约,版本 1.2.0:
{
"version": "1.2.0",
"fields": [
{ "name": "user_id", "type": "string", "required": true },
{ "name": "nickname", "type": "string", "required": true },
{ "name": "email", "type": "string", "required": false }
]
}
现在要加一个 phone 字段,而且是可选的。这属于向后兼容的新增,版本号升到 1.3.0。但如果要把 nickname 改成 username,这就是不兼容变更,得直接跳到 2.0.0。
我的习惯:在契约文件的元数据里直接写上版本号,同时用 changelog 字段记录每次变更的原因。这样任何人打开文件,一眼就知道当前版本和改了什么。
4.2 向后兼容性:不是所有变化都叫“升级”
向后兼容,说白了就是:我升级了契约,你老代码还能跑。
我曾经踩过一个坑。有个数据契约的 status 字段,枚举值从 ["active", "inactive"] 改成了 ["active", "suspended", "deleted"]。我心想这是新增枚举值,算 MINOR 升级。结果下游有个系统,代码里写死了 if status != "inactive" 的逻辑,新值 suspended 直接让它跑偏了。
所以,向后兼容不能只看“格式”,还得看“语义”。
4.2.1 哪些变更通常是向后兼容的?
- 新增可选字段(消费者不读就行)
- 新增枚举值(前提是消费者没有穷举判断)
- 放宽字段约束(比如从 required 改成 optional)
- 增加字段描述或注释
4.2.2 哪些变更一定不兼容?
- 删除字段
- 修改字段类型(比如 string 改成 int)
- 收紧字段约束(optional 改成 required)
- 修改枚举值的含义(比如 1 代表“男”改成 1 代表“女”)
- 重命名字段
避坑指南:我曾经以为“新增必填字段”是 MINOR 变更,结果上线后所有旧生产者都不发这个字段,下游直接报错。记住:新增必填字段 = MAJOR 变更,因为它破坏了现有生产者的兼容性。
4.3 版本演进策略:从 1.0.0 到 N.N.N 的路线图
版本号怎么升,不是拍脑袋决定的。我建议团队制定一个明确的演进策略,写在契约仓库的 CONTRIBUTING.md 里。
4.3.1 版本生命周期
每个版本可以有几个阶段:
- DRAFT(草稿):还在讨论中,不要用于生产
- PREVIEW(预览):基本确定,给消费者提前适配
- STABLE(稳定):正式发布,生产者必须遵守
- DEPRECATED(弃用):标记为旧版,建议迁移
- SUNSET(下线):不再提供服务
我习惯给每个版本加一个 status 字段,方便工具自动识别。
{
"version": "2.0.0",
"status": "PREVIEW",
"deprecation_date": "2025-06-30",
"sunset_date": "2025-09-30"
}
4.3.2 多版本共存策略
实际项目中,不可能所有消费者一夜之间都升级。所以你得同时维护多个版本。
我个人推荐“N-2”策略:同时维护当前最新版本和往前两个 MAJOR 版本。比如现在最新是 3.0.0,那 2.x.x 和 1.x.x 也要继续支持,但标记为 DEPRECATED。
| 版本 | 状态 | 支持截止日期 |
|---|---|---|
| 3.0.0 | STABLE | - |
| 2.5.0 | DEPRECATED | 2025-06-30 |
| 1.8.0 | SUNSET | 2025-03-31 |
我的经验:别让旧版本无限期存活。定好下线日期,提前三个月通知,到期坚决停掉。否则你会发现自己同时维护七八个版本,累死还不出活。
4.4 版本管理流程:从变更到发布
光有版本号规则不够,还得有流程。我画了一张图,把整个流程串起来。
流程看起来不复杂,但每个环节都有坑。我重点说几个容易翻车的地方。
4.3.3 变更评估:别只看字段名
评估兼容性的时候,很多人只看“字段增删改”。但实际中,语义变更比结构变更更隐蔽。
举个例子。有个字段叫 price,类型是 decimal,之前单位是“分”,现在改成“元”。结构上完全没变,但语义变了。消费者拿到 100,以为是 1 块钱,其实是 100 块。这种变更,必须走 MAJOR 版本。
我曾经犯过的错:有一次只改了字段描述,没改类型,觉得是 PATCH 变更。结果下游有个自动化脚本,根据描述做数据校验,直接崩了。从那以后,我规定:任何描述变更,只要影响消费者逻辑,都算 MINOR 以上。
4.3.4 预览期:给消费者留足时间
我建议 MAJOR 版本至少留 2-4 周的预览期。这段时间里:
- 生产者按新契约生产数据(双写或影子模式)
- 消费者适配新契约,跑回归测试
- 发现问题及时修复,发 PATCH 版本
预览期结束,确认没问题,再升为 STABLE。这时候旧版本就可以标记为 DEPRECATED 了。
4.5 工具化:让版本管理自动化
手动管理版本号,迟早会出错。我建议用工具来辅助。
常用的做法是:
- 把契约文件放在 Git 仓库里,每次变更走 PR 审查
- PR 模板里要求填写:变更类型(MAJOR/MINOR/PATCH)、兼容性分析、影响范围
- 用 CI 自动检查版本号是否按规则递增
- 发布时自动打 Git Tag,生成 Changelog
我的团队实践:我们写了一个小脚本,放在 CI 里。每次 PR 合并到 main 分支,脚本自动对比新旧契约,判断变更类型。如果开发者填的版本号和脚本判断的不一致,CI 直接拒绝合并。这招治好了很多人“随手改版本号”的毛病。
版本管理做到位了,数据契约才能真正成为团队协作的基石,而不是互相甩锅的导火索。记住:版本号是承诺,不是装饰。