protobuf(proto3)具备良好的向前和向后兼容性,但仍存在一些限制,本文就来讨论这些限制有哪些,以及在消息和API定义过程中的一些良好实践。
message
不要改变字段类型
字段类型一定要确保不变,当字段类型发生变化,需要增加新的字段,而不是修改已有字段。
虽然 int32, uint32, int64, uint64、和 bool 都是兼容的,但可能出现数据值溢出,也不建议相互修改
string和 有效 UTF-8 编码的bytes类型是兼容的
对于
string、bytes和 message 类型字段,设置/修改optional和repeated是兼容的。如果将repeated字段的序列化数据作为输入,预期该字段为optional的客户端会在原始类型字段的情况下获取最后一个输入值,或者在消息类型字段的情况下合并所有输入元素。所以最好的方案就是避免这样做,建议作废此字段。
不要改变字段编号
字段编号和字段类型也是一样的,需要确保稳定不变。
保留字段编号
当一个字段编号被废弃后不应再重复使用,建议使用 保留字段编号 来保留编号,避免被其它字段占用。enum 类型的字段也可以使用。
message Foo {
reserved 2, 15, 9 to 11;
}
这样,2、15、9 到 11 都会被保留下来不允许使用。随着时间推移,保留字段编号可以减少冲突的可能。
保留字段名称
reserved 也可以使用保留字段名称。当涉及到需要使用 TextProto 或 JSON 编码时字段名被序列化,使用重复的字段名会可能会引发错误。
message Bar {
reserved "foo", "bar";
}
标记 deprecated
在实际业务中当确实需要废弃某些字段时,应该先使用 deprecated 进行标记,并至少保留一个兼容版本,以提醒使用者尽快更新使用新的字段。
string old_field = 1 [deprecated = true];
注意 map<K, V> 的兼容问题
有的语言可能对生成的 map 类型重排序或者删除重复 key,所以在使用 map 类型时不要依赖 key 的顺序,并且避免插入重复键(若你的语言支持)。
oneof
类似消息字段,也要避免对 oneof 内部字段类型的修改和编号重复。
package
pacakge 作为命名空间也应保持稳定,避免出现命名冲突或找不到对应消息、服务的情况。
- 当使用
Any类型时,消息的全限定名将被序列化到二进制数据中 - gRPC 服务的 URI 路径是使用
package作为前缀的,例如/getting.v1.Auth/Signin