版本管理基础:版本号规范(SemVer)、版本历史记录、版本标签与分支策略
聊数据契约的版本管理,咱们得先打好地基。你想想看,一个数据契约从诞生到废弃,中间可能要经历几十次甚至上百次变更。如果没有一套清晰的版本管理机制,那场面……嗯,我在项目里见过,简直就是一场灾难。
今天这一节,我就把版本管理最核心的四个东西掰开揉碎了讲:版本号怎么定、历史怎么记、标签怎么打、分支怎么管。这些看起来都是基础活,但恰恰是这些基础活,决定了你后续变更控制能不能跑得顺。
2.1 版本号规范:SemVer 在数据契约中的落地
版本号这事儿,业界已经有成熟方案了——语义化版本控制(SemVer)。格式很简单:MAJOR.MINOR.PATCH,比如 1.3.2。
但数据契约和普通软件包不太一样。我个人习惯把 SemVer 的规则稍微调整一下,让它更贴合数据场景:
| 版本位 | 数据契约中的含义 | 变更示例 |
|---|---|---|
| MAJOR | 不兼容的 Schema 变更 | 删除字段、修改字段类型、改变主键 |
| MINOR | 向后兼容的新增 | 新增可选字段、扩展枚举值 |
| PATCH | 不影响 Schema 的修复 | 修正描述文字、调整约束条件 |
核心原则:消费者只依赖 MAJOR.MINOR 版本,PATCH 版本对消费者完全透明。一旦发布,MAJOR.MINOR 组合不可修改。
举个例子。假设你有一个用户信息契约,版本 2.1.0。现在要新增一个 phone 字段,而且是可选字段——这属于向后兼容,版本升到 2.2.0。但如果要把 email 字段从必填改成可选,或者干脆删掉它,那就是 3.0.0 了。
我曾经在一个项目中遇到过团队把 PATCH 版本当草稿箱用,一天发了十几个 1.0.x。结果消费者根本分不清哪个是稳定版。后来我定了个规矩:PATCH 版本只允许在预发布阶段使用,正式发布后一律冻结。
2.2 版本历史记录:谁、什么时候、改了什么
光有版本号还不够。你得知道每个版本背后发生了什么。我建议每个数据契约都维护一份 CHANGELOG,格式可以参考 keepachangelog.com 的标准。
一个合格的版本历史记录应该包含:
- 版本号:对应 SemVer 规范
- 发布日期:精确到日
- 变更类型:Added / Changed / Deprecated / Removed / Fixed / Security
- 变更描述:一句话说清楚改了啥
- 责任人:谁提的变更
- 关联工单:对应 Jira 或 GitHub Issue 编号
给你看个我常用的模板:
# Changelog
## [3.0.0] - 2025-06-15
### Removed
- 删除 `age` 字段(违反 GDPR 合规要求)
### Changed
- `email` 字段类型从 VARCHAR(100) 改为 VARCHAR(256)
### Added
- 新增 `consent_status` 枚举字段(可选)
## [2.2.0] - 2025-05-20
### Added
- 新增 `phone` 可选字段
- 扩展 `gender` 枚举值:新增 "non_binary"
## [2.1.0] - 2025-04-10
### Fixed
- 修正 `created_at` 字段的时区描述
我的习惯:CHANGELOG 和契约文件放在同一个仓库里,每次版本发布时一起提交。这样历史记录和代码变更永远是对应的,不会出现「改了契约忘了写日志」的情况。
2.3 版本标签:给每个发布版本打上标记
版本标签说白了就是给 Git 仓库里的某个提交打一个「书签」。我建议每个数据契约的正式发布版本,都在 Git 上打一个 annotated tag(带注释的标签)。
标签命名规则:
- 正式版:
v{MAJOR}.{MINOR}.{PATCH},例如v2.1.0 - 预发布版:
v{MAJOR}.{MINOR}.{PATCH}-{预发布标识},例如v2.2.0-rc.1 - 元数据:可在末尾加
+{元数据},例如v2.1.0+build.20250615
打标签的命令很简单:
# 创建带注释的标签
git tag -a v2.1.0 -m "Release v2.1.0: 新增 phone 可选字段"
# 推送到远程
git push origin v2.1.0
# 查看所有标签
git tag -l 'v2.*'
注意:标签一旦推送,尽量不要删除或修改。如果发现版本有问题,正确的做法是发布一个 PATCH 版本,而不是回滚标签。我曾经见过有人删标签重打,结果 CI/CD 流水线直接崩了——因为下游已经缓存了旧标签的哈希值。
2.4 分支策略:Git Flow 还是 Trunk-Based?
分支策略这事儿,没有银弹。我根据数据契约的特点,推荐两种主流方案:
方案一:Git Flow(适合正式、多版本并行的场景)
- main:只存放已发布的正式版本
- develop:日常开发的主分支
- feature/*:每个新功能或变更单独开分支
- release/*:发布前的准备分支,只做 bug 修复
- hotfix/*:紧急修复分支,直接从 main 拉
方案二:Trunk-Based(适合快速迭代、单一版本线的场景)
- main:唯一长期分支,所有变更直接提交或通过短生命周期分支合并
- 短分支:最多存活 1-2 天,合并后立即删除
- 配合 Feature Toggle 控制未完成功能的可见性
我个人更倾向 Trunk-Based + 短分支 的组合。原因很简单:数据契约的变更频率通常比应用代码低,而且消费者需要的是「当前最新稳定版」的清晰视图。Git Flow 的分支太多,容易让人搞不清哪个才是「真理之源」。
但如果你维护的是对外发布的 SDK 或 API 契约,需要同时支持多个大版本(比如 v1.x 和 v2.x),那 Git Flow 的 release/* 和 hotfix/* 机制会更合适。
2.5 版本冻结与归档
最后聊一个容易被忽略的点:版本什么时候该「死」。
不是所有版本都要永远维护。我建议给每个数据契约版本设定一个生命周期:
- 活跃期:支持变更,消费者可以依赖
- 维护期:只接受 PATCH 级别的修复
- 冻结期:不再接受任何变更,但消费者仍可读取
- 归档期:从生产环境移除,仅保留历史记录
我曾经在一个项目中,发现有个 v1.0.0 的契约已经三年没人用了,但还在生产环境跑着。每次部署都要带上它,白白增加风险。后来我们做了版本清理,把超过 6 个月没有消费者的版本直接归档——部署时间缩短了 30%。
小技巧:在契约仓库里加一个 DEPRECATED.md 文件,记录每个版本的废弃原因和替代方案。这样后来的人看到旧版本,至少知道该往哪儿迁移。