2、兼容性基础概念:向前兼容、向后兼容、破坏性变更、非破坏性变更

聊数据契约治理,绕不开四个基础概念。我见过不少团队,契约写得挺漂亮,一上线就炸——说白了,就是没搞懂「兼容」到底是什么意思。

今天咱们把这四个词掰开揉碎。你理解透了,后面选策略才不会跑偏。

2.1 向后兼容(Backward Compatibility)

这是最常被提起的概念。新版本的消费者,能正常处理老版本生产者发出的数据。

举个例子:你的订单服务原来只传 order_id 和 amount,后来加了 currency 字段。如果新消费者收到旧数据(没有 currency),依然能正常处理——这就是向后兼容。

核心判断标准:老数据 + 新代码 = 正常运行

我在项目中遇到过一个问题:某个团队改了字段类型,把 price 从 string 改成 number。老数据里全是字符串,新消费者一读就抛异常。嗯,这就是典型的向后不兼容。

2.2 向前兼容(Forward Compatibility)

这个概念很多人会搞反。向前兼容指的是:老版本的消费者,能正常处理新版本生产者发出的数据。

你想想看,微服务架构里,服务升级不可能同时完成。总有老服务还在跑,新数据已经发过来了。这时候老消费者能不能扛住?

核心判断标准:新数据 + 老代码 = 正常运行

我曾经踩过一个坑:上游新增了一个必填字段 user_type,下游老服务根本不知道这个字段的存在。结果反序列化时直接报错。这就是向前不兼容。

兼容类型 数据方向 代码方向 典型场景
向后兼容 老数据 新代码 消费者先升级
向前兼容 新数据 老代码 生产者先升级

2.3 破坏性变更(Breaking Change)

破坏性变更,就是改了契约之后,老消费者或老生产者直接没法用了。说白了,升级必须同步,不能独立。

常见的破坏性变更包括:

  • 删除字段——消费者还在读,生产者不发了
  • 重命名字段——老代码按旧名字找,找不到
  • 改变字段类型——比如 int 变 string,序列化/反序列化全乱
  • 缩小取值范围——原来允许 1-100,现在只允许 1-10,老数据超限
  • 增加必填字段——老数据没有这个字段,新消费者不认

注意:破坏性变更不是不能做,而是要有计划地做。我见过最惨的一次,有人直接删了 status 字段,结果下游 12 个服务全部挂掉。那天的 on-call 电话,我到现在还记得。

2.4 非破坏性变更(Non-Breaking Change)

非破坏性变更,就是改了契约,老消费者和老生产者依然能正常工作。这是我们在数据契约治理里追求的理想状态。

常见的非破坏性变更包括:

  • 增加可选字段——老数据没有这个字段,新消费者给个默认值就行
  • 扩大取值范围——原来 1-10,现在 1-100,老数据都在范围内
  • 增加枚举值——老消费者不认识新值,但不会崩溃(前提是代码做了兜底)
  • 调整字段顺序——对于 JSON 或 Protobuf 这种不依赖顺序的格式,没问题

我的习惯:每次改契约之前,先问自己三个问题——

  1. 老数据还能被新代码读吗?(向后兼容)
  2. 新数据还能被老代码读吗?(向前兼容)
  3. 如果都不行,我能不能分两步走?(先加后删)

2.5 四者关系:一张图说清楚

下面这张图,是我做数据契约治理时经常拿出来看的。它把四个概念的关系画得很清楚。

兼容性四象限与变更类型 向后兼容 老数据 + 新代码 = 正常 增加可选字段 ✓ 扩大取值范围 ✓ 增加枚举值 ✓ 删除字段 ✗ 向前兼容 新数据 + 老代码 = 正常 增加可选字段 ✓ 扩大取值范围 ✓ 增加枚举值 ✓ 增加必填字段 ✗ 破坏性变更 必须同步升级 删除字段 ✗ 重命名字段 ✗ 改变字段类型 ✗ 缩小取值范围 ✗ 非破坏性变更 可独立升级 增加可选字段 ✓ 扩大取值范围 ✓ 增加枚举值 ✓ 调整字段顺序 ✓ 目标:尽量做非破坏性变更,同时保证向前和向后兼容

2.6 实战中的判断方法

光背概念没用,得能判断。我一般用这个流程:

  1. 看数据方向——是老数据配新代码,还是新数据配老代码?
  2. 看字段变化——是增、删、改、缩?
  3. 看默认值——老数据缺字段时,新代码有没有兜底?
  4. 看序列化格式——JSON 宽松,Protobuf 严格,Avro 有模式演化规则

一句话总结:向后兼容保升级,向前兼容保回滚。两者都保住,你才能在微服务里自由地改契约。

我记得有一次,团队急着上线一个新功能,直接改了字段类型。我说不行,得先加新字段,再弃用旧字段。他们嫌麻烦。结果上线当天,老消费者全崩了。从那以后,没人再敢跳过兼容性检查。

这四个概念,是数据契约治理的基石。后面讲策略选择,你会反复用到它们。现在花点时间理解透,后面就顺了。