第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 版本生命周期

每个版本可以有几个阶段:

  1. DRAFT(草稿):还在讨论中,不要用于生产
  2. PREVIEW(预览):基本确定,给消费者提前适配
  3. STABLE(稳定):正式发布,生产者必须遵守
  4. DEPRECATED(弃用):标记为旧版,建议迁移
  5. 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 版本管理流程:从变更到发布

光有版本号规则不够,还得有流程。我画了一张图,把整个流程串起来。

数据契约版本演进流程 提出变更请求 评估兼容性 向后兼容 → MINOR/PATCH 不兼容 → MAJOR 发布预览版 (PREVIEW) 收集反馈 & 适配 正式发布 (STABLE)

流程看起来不复杂,但每个环节都有坑。我重点说几个容易翻车的地方。

4.3.3 变更评估:别只看字段名

评估兼容性的时候,很多人只看“字段增删改”。但实际中,语义变更比结构变更更隐蔽。

举个例子。有个字段叫 price,类型是 decimal,之前单位是“分”,现在改成“元”。结构上完全没变,但语义变了。消费者拿到 100,以为是 1 块钱,其实是 100 块。这种变更,必须走 MAJOR 版本。

我曾经犯过的错:有一次只改了字段描述,没改类型,觉得是 PATCH 变更。结果下游有个自动化脚本,根据描述做数据校验,直接崩了。从那以后,我规定:任何描述变更,只要影响消费者逻辑,都算 MINOR 以上。

4.3.4 预览期:给消费者留足时间

我建议 MAJOR 版本至少留 2-4 周的预览期。这段时间里:

  • 生产者按新契约生产数据(双写或影子模式)
  • 消费者适配新契约,跑回归测试
  • 发现问题及时修复,发 PATCH 版本

预览期结束,确认没问题,再升为 STABLE。这时候旧版本就可以标记为 DEPRECATED 了。

4.5 工具化:让版本管理自动化

手动管理版本号,迟早会出错。我建议用工具来辅助。

常用的做法是:

  1. 把契约文件放在 Git 仓库里,每次变更走 PR 审查
  2. PR 模板里要求填写:变更类型(MAJOR/MINOR/PATCH)、兼容性分析、影响范围
  3. 用 CI 自动检查版本号是否按规则递增
  4. 发布时自动打 Git Tag,生成 Changelog

我的团队实践:我们写了一个小脚本,放在 CI 里。每次 PR 合并到 main 分支,脚本自动对比新旧契约,判断变更类型。如果开发者填的版本号和脚本判断的不一致,CI 直接拒绝合并。这招治好了很多人“随手改版本号”的毛病。

版本管理做到位了,数据契约才能真正成为团队协作的基石,而不是互相甩锅的导火索。记住:版本号是承诺,不是装饰。