如何在 protobuf 中实现一个真正可选的字段?

How to implement a really optional field in protobuf?

我有一个描述可能每月、每年或在特定日期发生的事件的原型。其基本结构是:

enum PeriodType {
  MONTHLY = 1;
  YEARLY = 2;
  SPECIFIC_DATE = 3;
}

message Event {
  PeriodType period_type = 1;
  ...
}

但是应该有一种方法可以指定发生的日期。所以我制作了以下原型:

message Event {
  PeriodType period_type = 1;
  Date period_specific_date = 2;
  ...
}

该解决方案在我们的内部代码审查期间被禁止。原因是"proto should not contain fields which are used only when there is a particular value in another field"。我需要以某种方式重写它,这样当 eventperiod_type == MONTHLY 时就不可能调用 event.getPeriodSpecificDate()。我该怎么做?

一个可能的解决方案是这样:

message Event {
  message Period {
    PeriodType period_type = 1;
  }
  message PeriodWithDate {
    Period period = 1;
    Date period_specific_date = 2;
  }
  oneof period {
    Period period_without_date = 1;
    PeriodWithDate period_with_date = 2;
  }
  ...
}

但这并不能真正解决问题,而且看起来有点矫枉过正。还有其他办法吗?

如果给出了日期,您实际上并不需要 PeriodType。 所以你可以这样做:

enum RegularPeriod
{
    MONTHLY = 1;
    YEARLY = 2;
}
oneof period
{
    RegularPeriod regular = 1;
    Date specific_date = 2;
}

That solution was banned during our internal code review. The reason is that "proto should not contain fields which are used only when there is a particular value in another field".

告诉你的评论者我是 Protocol Buffers v2 的作者,我认为这条规则是不现实的。虽然强制执行类型系统中的所有内容很诱人,但实际上 Protobuf 类型系统的表现力远不足以制定这种硬性规则。在 real-world 中,使用 "this field should only be present if field X has value Y" 等记录约束的字段实际上很常见,这真的没问题。

话虽如此,在这种特殊情况下,@jpa 的回答是合理的。它具有权衡:您现在将在代码中有多个级别的分支,这可能很丑陋,并且如果您的协议变得更复杂,可能会变得更丑陋。但它确实设法表达了类型系统中的所有约束。

(不要接受我的回答,接受@jpa的。)