版本管理基础:版本号规范(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/* 机制会更合适。

数据契约版本管理核心流程 版本号规范 MAJOR.MINOR.PATCH 版本历史记录 CHANGELOG 版本标签 Git Tag 分支策略 Git Flow / Trunk-Based 关键要点 • 版本号、历史记录、标签、分支策略四者缺一不可 • 版本号决定兼容性语义,历史记录提供审计线索 • 标签锁定发布点,分支策略规范协作流程 • 四者配合使用,才能实现可追溯、可回滚、可协作

2.5 版本冻结与归档

最后聊一个容易被忽略的点:版本什么时候该「死」。

不是所有版本都要永远维护。我建议给每个数据契约版本设定一个生命周期:

  • 活跃期:支持变更,消费者可以依赖
  • 维护期:只接受 PATCH 级别的修复
  • 冻结期:不再接受任何变更,但消费者仍可读取
  • 归档期:从生产环境移除,仅保留历史记录

我曾经在一个项目中,发现有个 v1.0.0 的契约已经三年没人用了,但还在生产环境跑着。每次部署都要带上它,白白增加风险。后来我们做了版本清理,把超过 6 个月没有消费者的版本直接归档——部署时间缩短了 30%。

小技巧:在契约仓库里加一个 DEPRECATED.md 文件,记录每个版本的废弃原因和替代方案。这样后来的人看到旧版本,至少知道该往哪儿迁移。