为什么在协议缓冲区3中删除了必需和可选项

yjz*_*ang 182 protocol-buffers grpc proto3

我最近使用gRPCproto3,我已经注意到,requiredoptional在新的语法已被删除.

有人会解释为什么在proto3中删除了必需/可选项?这样的约束似乎对于使定义具有鲁棒性是必要的.

语法proto2:

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;
  optional int32 result_per_page = 3;
}
Run Code Online (Sandbox Code Playgroud)

语法proto3:

syntax = "proto3";
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*son 332

它的用处required一直是许多辩论和火焰战争的核心.双方都有大型营地.一个阵营喜欢保证价值存在并且愿意忍受其限制,但另一个阵营感到required危险或无益,因为它无法安全地添加或删除.

让我解释为什么required应该谨慎使用字段的原因.如果您已经在使用proto,则无法添加必填字段,因为旧应用程序将不提供该字段,并且应用程序通常不能很好地处理故障.您可以确保首先升级所有旧应用程序,但它很容易出错,如果您将protos存储在任何数据存储区中(即使是短暂的,如memcached)也无济于事.删除必填字段时也会出现同样的情况.

许多必需的领域"显然"需要,直到......他们不是.假设你有id一个Get方法的字段.这显然是必需的.除此之外,您可能需要将idfrom int 更改为string,或将int32更改为int64.这需要添加一个新muchBetterId字段,现在您将留下必须指定的旧id字段,但最终会被完全忽略.

当这两个问题结合起来时,有益required领域的数量变得有限,并且营地争论它是否仍然具有价值.反对者required不一定反对这个想法,而是它目前的形式.一些人建议开发一个更具表现力的验证库,它可以检查required更先进的东西name.length > 10,同时也确保有更好的故障模型.

Proto3整体似乎更倾向于简单,并且required删除更简单.但也许更有说服力,required当与其他功能结合使用时删除了proto3,例如删除原语的字段存在以及删除重写的默认值.

我不是一个protobuf开发者,在这个问题上我没有权威,但我仍然希望这个解释很有用.

  • 是的.另请参阅对所需字段可能出现严重错误的扩展说明:https://capnproto.org/faq.html#how-do-i-make-a-field-required-like-in-protocol-buffers (21认同)
  • 我觉得protobuf是一种明确设计用于启动火焰战争的语言 (12认同)
  • 相反,似乎在proto3中删除了"可选".每个字段都存在,并使用默认值填充.您无法知道用户是填写了原始字段,还是默认情况下.消息字段(基本上是指针)是可选的,因为它们可以具有空值. (9认同)
  • 可选不删除; proto3中的一切都是可选的.但是,是的,已删除_primitives_的字段可见性(has_field).如果您需要字段可见性,请使用[wrappers.proto](https://github.com/google/protobuf/blob/master/src/google/protobuf/wrappers.proto),其中包含"StringValue"等消息.由于它们是消息,因此has_field可用.这实际上是"拳击",这在许多语言中很常见. (8认同)
  • 似乎大多数人不想对其 API 进行版本控制。他们更容易使“向后兼容性”的所有内容都成为可选项。 (8认同)
  • 我希望在创建新消息时根据需要标记字段,但在解析字符串时则不这样做。假设我添加了一个新字段,那么我想确保所有代码实际上都设置了该新字段。但是来自旧代码或存储的消息可以获得默认值。 (3认同)
  • 这一切都很好,但是 - 我如何判断是否未提供字段或收到默认字段?对于零索引的东西,有点奇怪的是,如果 API 调用无法提供索引,则该索引默认为零,并且绝对没有办法告诉它没有提供。 (3认同)
  • 我不知道使用'required'是否会引起任何麻烦,但是删除'optional'和'has_field'肯定会。即使在最简单的情况下,它的两个可能值都具有含义,这会迫使您重新发明方向盘或向api添加更多参数,而不是仅仅依赖于基础结构层 (2认同)
  • @FabianHertwig,int32,uint32,int64,uint64和bool是线兼容的,但它们不一定是API兼容的.更改.proto可能会导致应用程序无法在运行时编译或中断.因此,除非您对所有用法进行严格控制并且几乎没有,否则引入新字段而不是更改现有字段可能会更好. (2认同)

小智 32

你可以在这个protobuf Github问题中找到解释:

我们删除了proto3中的必填字段,因为通常认为必填字段有害并且违反了protobuf的兼容性语义.使用protobuf的整个想法是,它允许您添加/删除协议定义中的字段,同时仍然完全向前/向后兼容较新/较旧的二进制文件.必填字段打破了这一点.您永远不能安全地向.proto定义添加必填字段,也不能安全删除现有的必填字段,因为这两个操作都会破坏电线兼容性.例如,如果向.proto定义添加必填字段,则使用旧定义构建的二进制文件将无法解析使用旧定义序列化的数据,因为旧数据中不存在必需字段.在一个复杂的系统中,.proto定义在系统的许多不同组件中广泛共享,添加/删除必需的字段可以轻松地降低系统的多个部分.我们已经多次看到由此造成的生产问题,并且谷歌内部的任何地方都禁止添加/删除必填字段.出于这个原因,我们完全删除了proto3中的必填字段.

删除"required"后,"optional"只是多余的,所以我们也删除了"optional".

  • 我不明白;反序列化后丢弃消息和反序列化时丢弃消息有什么区别?它会被旧客户端删除,因为它不包含需要的字段(例如 id)。 (13认同)
  • 我完全同意@ShmuelH。API 中需要以某种方式提供字段,这对于客户了解这一点很有用。这让我觉得我们还没有做好版本控制。 (12认同)
  • 另一次投票给@ShmuelH。如果您以向后不兼容的方式更改 API(添加必填字段),那么您肯定希望解析器能够检测到这一点吗?对您的 API 进行版本控制!如果您愿意,您甚至可以完全在 Protobuf 中使用“oneof { MessageV1, MessageV2, etc. }”来完成此操作。 (9认同)
  • 这实际上可以归结为这样一个事实:必需/可选强制将两个关注点捆绑在一起;第一个是序列化,第二个是应用程序数据验证。这种捆绑会导致所描述的问题,这意味着您确实希望在其他地方进行验证。让序列化层包含此验证是错误的,尽管理想情况下您希望它能够轻松地进行互操作。 (4认同)
  • 我倾向于同意@ShmuelH。必填字段将以某种方式成为api的一部分。嗯,这是通过提供给双方的语法自动支持的,或者隐藏在后端中,它仍然存在。也可以使其在api定义中可见 (2认同)
  • 它不能证明最初有必填字段是合理的。添加必填字段是不兼容的更改,通常应通过协议版本更改(即新的消息类型)来处理。 (2认同)
  • @ShmuelH。如果您在多种服务的背景下思考,它会更有意义。例如,如果亚马逊有一项返回产品列表的服务和另一项用于更新产品的服务,您可以在这两项服务中使用相同的产品消息。为了获取要在主页上呈现的产品,客户端不需要知道ownerId。对于发送要更新的产品有效负载的客户端,可能需要存在ownerId。独立服务可以决定他们需要哪些字段,而无需在每个客户端的 API 级别强制执行。 (2认同)
  • @patrickbarker 版本控制不会有帮助。向后兼容性涉及现有代码,但使用旧代码的客户端也可以添加可能想要使用新字段的新代码,而无需更新不需要该字段的旧代码。您可以添加一个新字段,该字段应该是一个客户必需的,但对其他客户隐藏。之后,您可以添加一个您希望所有客户都能看到的更新字段。在这种情况下,您希望客户端代码同时使用架构的三个迭代(版本)中的字段,而不仅仅是单个版本。 (2认同)

z0m*_*1ek 7

可选字段在 protobuf 3.15 中返回

  • @SubinSebastian 使用可选选项,您可以显式检查是否设置了字段。假设您有一个字段“int32confidence”。目前,当收到此类类型的消息时,您无法知道“confidence = 0”或“confidence not set”之间的区别。因为默认值在序列化中被优化掉了。如果您将该字段标记为“可选”,那么可能会在序列化中设置一些额外的位,并且将生成“has_confidence()”方法,以便接收端的您可以消除两者的歧义。 (21认同)
  • 如果一切都是可选的,那么在上述版本中返回“可选”有什么用呢? (4认同)
  • @SubinSebastian 参见 https://github.com/protocolbuffers/protobuf/blob/master/docs/field_presence.md (3认同)

Sen*_*ith 6

因为相关概念的正交分解很困难,并且 Protocol Buffers 设计以一种令人沮丧的方式组合了至少 4 个独立的概念:可空性(又名存在跟踪)、内容验证、有用的默认值和空间效率。

Proto2 允许字段为必填”或“可选”,并允许指定默认值,但仅限于“可选”字段。

“可选”关键字在 3.12/3.15 中回归,用于状态跟踪。请参阅Field Presence 上的应用说明

“required”关键字已被用作验证检查,这很不幸,因为 Protobuf 无法胜任验证工具的任务。它没有最小/最大值语法、长度或模式限制。更重要的是,它无法表达字段之间的数据值依赖关系(除了基本结构固有的关系)。

Protobuf 使用“默认”值作为初始构造值,并作为一种通过不发送这些值来减少消息大小的机制(在 proto3 中)。这有点不幸,因为能够为特定代码生成运行指定初始值对于生产者(“这是我想要在正常使用中发送的值”)或消费者(“也许我应该检查是否为空/不存在,但现在就好像发送“x”一样就足够了“)。

OTOH,如果未提供值,指定合同默认假设如何继续似乎确实会有所帮助。然而,能否做好这方面的工作通常取决于其他字段的值(或存在),而 Protobuf 无法做到这一点,因为同样(可以说很漂亮)缺乏复杂性,这使得它不适合数据验证。因此,如果您希望在面对缺失字段时表现良好,那么最好将显式存在检查与任何其他适当的数据检查结合起来。.... 至少在 3.12/3.15 以后你可以。...对于简单的情况,也许 0 左右的值就足够了。