协议缓冲区如何处理版本控制?

Cod*_*ero 34 protocol-buffers protobuf-net

协议缓冲区如何处理类型版本控制?

例如,当我需要随时间更改类型定义时?喜欢添加和删除字段.

Mar*_*ell 24

谷歌设计的protobuf对版本化非常宽容:

  • 意外数据要么存储为"扩展"(使其成为往返安全),要么默默地删除,具体取决于实现
  • 新字段通常添加为"可选",这意味着可以成功加载旧数据

然而:

  • 重新编号字段 - 这将破坏现有数据
  • 你通常不应该改变任何给定字段的存储方式(即从固定的32位int到"varint")

一般来说,虽然 - 它只会工作,你不需要担心版本控制.

  • 版本控制支持将是一个强大的功能,Google协议缓冲区和Thrift都缺乏这一功能.我认为这个答案是关于一个不同的概念(对参数错误的容忍度)而不是类型版本控制. (11认同)
  • 据推测,删除_required_字段还会导致问题吗? (2认同)
  • @jon你最好没有任何字段实际上被定义为"必需"因为这个原因.proto缓冲区文档中讨论了此问题. (2认同)
  • @m-ric我理解其基本原理,但我不确定我是否同意.通过版本控制,不仅可以引入新字段,而且可以以干净的方式弃用旧字段,而不是原型.Proto3因为这个特殊原因已经删除了"必需",因为如果没有版本控制的概念,它们会让人感到困惑和冒险(http://stackoverflow.com/questions/31801257/why-required-and-optional-is-removed-in -protocol缓冲器-3).自描述格式也可以通过版本控制来完成,所以我不认为它是protobuf的优势. (2认同)

Jam*_*yce 7

我知道这是一个老问题,但是最近我遇到了这个问题。我解决这个问题的方法是使用Facades和运行时决策进行序列化。这样,我可以将旧的字段和新的消息优雅地处理,以将其弃用/升级为新的字段。

我正在使用Marc Gravell的protobuf.net(v2.3.5)和C#,但是外观理论适用于任何语言和Google的原始protobuf实现。

我的旧班级有一个DateTime时间戳记,我想对其进行更改以包括“ Kind”(。NET过时)。有效地添加它意味着它序列化为9个字节而不是8个字节,这将是一个重大的序列化更改!

    [ProtoMember(3, Name = "Timestamp")]
    public DateTime Timestamp { get; set; }
Run Code Online (Sandbox Code Playgroud)

protobuf的基本原理是永远不要更改protoid。我想阅读旧的序列化二进制文件,这意味着“ 3”将保留下来。

所以,

我重命名了旧属性并将其设为私有(是的,它仍然可以通过反射魔术来反序列化),但是我的API不再显示它可用!

    [ProtoMember(3, Name = "Timestamp-v1")]
    private DateTime __Timestamp_v1 = DateTime.MinValue;
Run Code Online (Sandbox Code Playgroud)

我使用新的原型ID创建了一个新的Timestamp属性,并包含了DateTime.Kind

    [ProtoMember(30002, Name = "Timestamp", DataFormat = ProtoBuf.DataFormat.WellKnown)]
    public DateTime Timestamp { get; set; }
Run Code Online (Sandbox Code Playgroud)

对于旧邮件,我添加了“ AfterDeserialization”方法来更新新时间

    [ProtoAfterDeserialization]
    private void AfterDeserialization()
    {
        //V2 Timestamp includes a "kind" - we will stop using __Timestamp - so keep it up to date
        if (__Timestamp_v1 != DateTime.MinValue)
        {
            //Assume the timestamp was in UTC - as it was...
            Timestamp = new DateTime(__Timestamp_v1.Ticks, DateTimeKind.Utc)     //This is for old messages - we'll update our V2 timestamp...
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在,我可以正确地对旧消息和新消息进行序列化/反序列化,并且我的时间戳现在包含DateTime.Kind!没坏。

但是,这确实意味着两个字段都将出现在所有新消息中。因此,最后的选择是使用运行时序列化决策来排除旧的时间戳(请注意,如果它使用了protobuf的required属性,它将无法正常工作!!!)

    bool ShouldSerialize__Timestamp_v1() 
    {
        return __Timestamp_v1 != DateTime.MinValue;
    }
Run Code Online (Sandbox Code Playgroud)

就是这样。我有一个不错的单元测试,如果有人想要的话,可以从头到尾进行...

我知道我的方法依赖于.NET魔术,但是我认为该概念可以翻译成其他语言。

  • 解决一个老问题不需要道歉。它们并没有腐烂,下一个开发者也会从你的经验中受益。谢谢你! (2认同)