通过总是添加新字段来演化序列化框架(如 ProtoBuf、Thrift 等)的数据模式有什么问题?

What is the problem of evolving a data schema of a serialization framework (like ProtoBuf, Thrift, etc.) by always adding new fields?

我正在编写一个简单的序列化框架。我的意图是 而不是 与 ProtoBuf、Thrift、Avro 等竞争,远非如此。我的目标是学习。

我的问题与发展我的数据对象的架构有关。

假设我有两个程序 A 和 B,它们需要交换数据并且我有一个由以下模式表示的数据对象:

public byte[] accountUser = new byte[8]; // first field

太棒了!现在我想继续向我的数据对象模式添加一个 accountId 字段:

public byte[] accountUser = new byte[8]; // first field
public int accountId = -1; // second field just added (NEW FIELD)

场景 1:

程序 A 具有带 accountId 的新架构,而程序 B 没有。

程序A将数据对象发送给程序B。

程序B 将简单地读取数据直到accountUser 并完全忽略accountId。它对此一无所知,也没有更新为使用带有 accountId 的最新数据对象模式。

一切正常!

场景 2:

程序 A 的旧模式没有 accountId,程序 B 的新模式有 accountId。

程序A将数据对象发送给程序B。

程序B 将读取数据直到accountUser 并继续尝试读取新的accountId。但是在接收到的数据对象中没有更多可读的内容。 accountUser 之后没有更多数据。因此,程序 B 只是假定 accountId 的默认空值 -1 并继续其生命。我很可能有逻辑来处理仍然使用旧模式运行的遗留系统中的 -1 accountId。

一切正常!

那么这种模式演进的简单方法真正的问题是什么?我知道它不完美,但它不能成功使用吗?我只需要假设我永远不会删除任何字段并且我永远不会弄乱字段的顺序。我只是不断添加更多字段。

通过某种 header 协议本身 field-based 本身添加新字段不是问题。显然,如果它是基于 size/blit 的,则会出现问题,因为它会读取每条记录的错误数据量。添加字段正是大多数协议的工作方式,所以这不是问题但是解码器确实需要提前知道如何忽略一个它不理解的领域。它会跳过一些固定数量的字节吗?它会寻找一些关闭哨兵吗?还有别的吗?只要你的解码器知道如何忽略它不知道的每一种可能的字段:你没问题。

你也不应该假设简单的增量字段,IMO。我已经看到,在现实世界的场景中,一个结构被不同的团队以两种不同的方式分支,然后重新组合,所以

的每个组合
  • 一个
  • A、B
  • A、C
  • A、B、C

(其中 B 和 C 是不同的附加字段集)是可能的

I just have to assume that I will never remove any field and that I will never mess with the order of the fields.

这会发生。你需要处理它;或者承认你正在解决一个更简单的问题,所以你的解决方案更简单。

What is the problem of evolving a data schema of a serialization framework by always adding new fields?

这是你的文件。所有二进制数据。

+------------------+
| accountUser      | 
| accountId        | 
+------------------+
| accountUser      | 
| accountId        | 
+------------------+
| accountUser      | 
| accountId        | 
+------------------+

现在让您的老客户(不知道 accountId 的客户)阅读条目。

What is the problem of evolving a data schema of a serialization framework by always adding new fields?

你的基本前提是错误的。在实践中,您也会发生...

  • remove/deprecate 字段
  • 不要写没有值的可选字段
  • 需要跳过未知字段(如上)

这些框架解决的不仅仅是一个问题。

我看到添加字段仍然导致失败的一种情况是使用 Union。让我们看一个例子:

Union {
  string firstName
  string lastName
}

现在假设我们添加 string middleName。程序 A 是最新的并发送带有变量 middleName 的程序 B 但它会在这里失败,因为程序 B 不知道 middleName 并且因此当它尝试反序列化对象时所有字段都将为 null 这对于导致失败的联合对象来说是不好的