当你第一次定义Protocol Buffer的消息的时候,你确定会给消息设定一套规则需求。可是随着时间的推动,你的业务可能会发生了变化,与此同时,你的Protocol Buffer消息类型的需求也会随之变化。
也就是说:有一些字段可能会发生变化,可能会添加一些字段,也可能会删除一些字段。可是可能有不少程序正在使用/读取你的Protocol Buffer的消息,可是它们无法都随着需求进行更新。因此,在你对源数据进行演进的时候,必定不要引发破坏性变化,不然其它的程序可能就没法正常工做了。
主要有这两种情景:
- 向前兼容变动:使用新的.proto文件来写数据 --- 从旧的.proto文件读取数据
- 向后兼容变动:使用旧的.proto文件来写数据 --- 重新的.proto文件读取数据
有时候这两种状况同时存在,也就是全兼容变动。
为了达到此目的,Protocol Buffer制定了一些更新消息类型的规则:
- 不要修改任何现有字段的数字(tag)
- 你能够添加新的字段,那些使用旧的消息格式的代码仍然能够将消息序列化,您应该注意这些元素的默认值,以便新代码能够与旧代码生成的消息正确交互。相似的,新代码所建立的消息也能够被旧代码解析:旧的二进制在解析的时候会忽略新的字段。
- 字段能够被删除,只要它们的数字(tag)在更新后的消息类型中再也不使用便可。你也能够把字段名改成使用“OBSOLETE_”前缀而不是删除字段,或者把这些字段的数字(tag)进行保留(reserved),以避免将来其它开发者不消息使用了删除字段的数字。
- 对于数据类型的变化,例如int32到int64,string到bytes等等,能够参考官方文档:
https://developers.google.com/protocol-buffers/docs/proto3#updating。 可是建议仍是尽可能不要去修改字段的数据类型。
添加字段
原来的proto是这样的:
而后我添加一个name字段:
而这时,若是把新的消息发送到旧的代码的时候,旧代码不知道2这个数字tag对应的是什么,因此name这个字段就会被忽略掉。
反过来,若是咱们使用新的代码读取旧的数据,那么就会找不到新的字段,这时候就会使用该字段类型的默认值(空字符串)。
因此,处理默认值的时候必定要很是的当心。
对字段重命名
如今我把name这个字段的名改为了full_name,而它的数字不变:
这样作是没有任何问题的。
你能够随意改变字段的名字,只要它的数字tag不变就行,由于Protocol Buffer里面这个数字tag才是最重要的。
删除字段
如今我又把full_name字段删除了:
这时候,若是旧的代码找不到这个字段了,那么就会采用默认值。
反过来,若是咱们使用新的代码读取旧的数据,那么已删除的字段将会被忽略/丢弃。
可是,在删除字段的时候,你应该一直都保留字段的数字tag以及字段名,像这样:
这样作是防止数字tag和名称被重复使用,避免在之后的代码库里形成冲突。
使用OBSOLETE
以前说了,能够把字段名改成 OBSOLETE_字段名 来代替删除字段,可是这样作的缺点就是:你仍是须要把这个字段的值计算出来。我仍是建议使用reserve的方式进行删除字段的管理。
Reserved
- 你能够保留字段的数字tag和字段名;
- 可是不能够在同一行语句里混合reserved数字tag和字段名,应该分红两个语句:
- 保留字段数字tag的目的就是防止数字tag被重复使用;
- 而保留字段名的目的就是防止出现一些程序bug;
注意:必定不要移除reserved的数字tags。
默认值
默认值在更新Protocol Buffer消息定义的时候有很重要的做用,它能够防止对现有代码/新代码形成破坏性影响。它们也能够保证字段永远不会有null值。
可是,默认值仍是很是危险的:
- 你没法区分这个默认值究竟是来自一个丢失的字段仍是字段的实际值正好等于默认值。
那么应该怎么办?
- 须要保证这个默认值对于业务来讲是一个毫无心义的值。例如 int32 pop(人口)默认值就能够设置为-1。
- 再就是,可能须要在你的代码里来作一些对默认值的判断,从而进行处理。
枚举
enum一样能够进化,就和消息的字段同样,能够添加、删除值,也能够保留值。
可是若是代码不知道它接收到的值对应哪一个enum值,那么enum的默认值将会被采用。
例如这个enum:
若是程序代码接收到了5这个数值,那么它找不到对应的枚举值,因此就会使用这个枚举的默认值0(UNSPECIFIED)。