Protobuf Editions 概述
Protobuf Editions 取代了我们用于 Protocol Buffers 的 proto2 和 proto3 标记。现在,您不再需要在 proto 定义文件的顶部添加 syntax = "proto2"
或 syntax = "proto3"
,而是使用版本号,例如 edition = "2023"
,来指定文件将具有的默认行为。Editions 使语言能够随时间逐步演进。
与旧版本中硬编码的行为不同,Editions 表示了一系列 特性,每个特性都有一个默认值(行为)。特性是文件、消息、字段、枚举等上面的选项,它们指定了 protoc、代码生成器和 protobuf 运行时的行为。当您的需求与所选版本的默认行为不符时,您可以在不同的级别(文件、消息、字段等)上明确覆盖某个行为。您也可以覆盖您的覆盖。本主题后面关于 词法作用域 的部分将对此进行更详细的介绍。
最新发布的版本是 2023。
特性生命周期
Editions 为特性的生命周期提供了基本增量。特性具有预期的生命周期:引入、更改其默认行为、弃用,然后移除。例如
2031 版本创建了
feature.amazing_new_feature
,其默认值为false
。此值保持与所有早期版本相同的行为。也就是说,它默认没有影响。开发者将其 .proto 文件更新为
edition = "2031"
。后续版本(例如 2033 版本)将
feature.amazing_new_feature
的默认值从false
切换到true
。这是所有 proto 文件所需的行为,也是 protobuf 团队创建该特性的原因。使用 Prototiller 工具将早期版本的 proto 文件迁移到 2033 版本时,会根据需要添加明确的
feature.amazing_new_feature = false
条目,以继续保留先前的行为。当开发者希望新行为适用于其 .proto 文件时,可以移除这些新添加的设置。
在某个时候,
feature.amazing_new_feature
在某个版本中被标记为已弃用,并在后续版本中被移除。移除某个特性时,该行为的代码生成器和支持它的运行时库也可能被移除。不过,时间线会比较宽松。按照生命周期早期步骤中的示例,弃用可能发生在 2034 版本,但直到大约两年后的 2036 版本才会被移除。移除特性总是会启动一个主要版本更新。
由于此生命周期,任何 .proto
文件从一个版本升级到下一个版本都是无操作的,前提是它不使用已弃用特性。您将拥有完整的 Google 迁移窗口加上弃用窗口来升级您的代码。
前面的生命周期示例使用了布尔值作为特性,但特性也可以使用枚举。例如,features.field_presence
具有值 LEGACY_REQUIRED
、EXPLICIT
和 IMPLICIT。
迁移到 Protobuf Editions
Editions 不会破坏现有二进制文件,也不会更改消息的二进制、文本或 JSON 序列化格式。第一个版本尽可能地减少了破坏性。第一个版本建立了基线,并将 proto2 和 proto3 定义合并到一个新的单一定义格式中。
后续版本发布后,特性的默认行为可能会发生变化。您可以让 Prototiller 对您的 .proto 文件进行无操作转换,或者您可以选择接受部分或全部新行为。Editions 计划每年发布一次。
从 Proto2 到 Editions
本节展示了一个 proto2 文件,以及运行 Prototiller 工具将定义文件更改为使用 Protobuf Editions 语法后可能的样子。
Proto2 语法
// proto2 file
syntax = "proto2";
package com.example;
message Player {
// in proto2, optional fields have explicit presence
optional string name = 1 [default = "N/A"];
// proto2 still supports the problematic "required" field rule
required int32 id = 2;
// in proto2 this is not packed by default
repeated int32 scores = 3;
enum Handed {
HANDED_UNSPECIFIED = 0;
HANDED_LEFT = 1;
HANDED_RIGHT = 2;
HANDED_AMBIDEXTROUS = 3;
}
// in proto2 enums are closed
optional Handed handed = 4;
reserved "gender";
}
Editions 语法
// Edition version of proto2 file
edition = "2023";
package com.example;
option features.utf8_validation = NONE;
message Player {
// fields have explicit presence, so no explicit setting needed
string name = 1 [default = "N/A"];
// to match the proto2 behavior, LEGACY_REQUIRED is set at the field level
int32 id = 2 [features.field_presence = LEGACY_REQUIRED];
// to match the proto2 behavior, EXPANDED is set at the field level
repeated int32 scores = 3 [features.repeated_field_encoding = EXPANDED];
enum Handed {
// this overrides the default edition 2023 behavior, which is OPEN
option features.enum_type = CLOSED;
HANDED_UNSPECIFIED = 0;
HANDED_LEFT = 1;
HANDED_RIGHT = 2;
HANDED_AMBIDEXTROUS = 3;
}
Handed handed = 4;
reserved gender;
}
从 Proto3 到 Editions
本节展示了一个 proto3 文件,以及运行 Prototiller 工具将定义文件更改为使用 Protobuf Editions 语法后可能的样子。
Proto3 语法
// proto3 file
syntax = "proto3";
package com.example;
message Player {
// in proto3, optional fields have explicit presence
optional string name = 1 [default = "N/A"];
// in proto3 no specified field rule defaults to implicit presence
int32 id = 2;
// in proto3 this is packed by default
repeated int32 scores = 3;
enum Handed {
HANDED_UNSPECIFIED = 0;
HANDED_LEFT = 1;
HANDED_RIGHT = 2;
HANDED_AMBIDEXTROUS = 3;
}
// in proto3 enums are open
optional Handed handed = 4;
reserved "gender";
}
Editions 语法
// Editions version of proto3 file
edition = "2023";
package com.example;
message Player {
// fields have explicit presence, so no explicit setting needed
string name = 1 [default = "N/A"];
// to match the proto3 behavior, IMPLICIT is set at the field level
int32 id = 2 [features.field_presence = IMPLICIT];
// PACKED is the default state, and is provided just for illustration
repeated int32 scores = 3 [features.repeated_field_encoding = PACKED];
enum Handed {
HANDED_UNSPECIFIED = 0;
HANDED_LEFT = 1;
HANDED_RIGHT = 2;
HANDED_AMBIDEXTROUS = 3;
}
Handed handed = 4;
reserved gender;
}
词法作用域
Editions 语法支持词法作用域,并为每个特性提供了允许的目标列表。例如,在第一个版本中,特性只能在文件级别或最低的粒度级别上指定。词法作用域的实现使您能够在整个文件中设置某个特性的默认行为,然后在消息、字段、枚举、枚举值、oneof、服务或方法级别上覆盖该行为。在较高层次(文件、消息)上进行的设置在同一作用域(字段、枚举值)内未进行任何设置时适用。任何未明确设置的特性都遵循用于 .proto 文件的版本中定义的行为。
以下代码示例展示了在文件、字段和枚举级别设置的一些特性。
edition = "2023";
option features.enum_type = CLOSED;
message Person {
string name = 1;
int32 id = 2 [features.field_presence = IMPLICIT];
enum Pay_Type {
PAY_TYPE_UNSPECIFIED = 1;
PAY_TYPE_SALARY = 2;
PAY_TYPE_HOURLY = 3;
}
enum Employment {
option features.enum_type = OPEN;
EMPLOYMENT_UNSPECIFIED = 0;
EMPLOYMENT_FULLTIME = 1;
EMPLOYMENT_PARTTIME = 2;
}
Employment employment = 4;
}
在前面的示例中,存在性特性设置为 IMPLICIT
;如果未设置,则默认为 EXPLICIT
。Pay_Type
enum
将为 CLOSED
,因为它应用了文件级别的设置。但是,Employment
enum
将为 OPEN
,因为它在枚举内部设置了。
Prototiller
Prototiller 工具发布后,我们将提供迁移指南和迁移工具,以简化到 Editions 以及 Editions 之间的迁移。该工具将使您能够
- 大规模地将 proto2 和 proto3 定义文件转换为新的 editions 语法
- 将文件从一个版本迁移到另一个版本
- 以其他方式操作 proto 文件
向后兼容性
我们正在构建 Protobuf Editions,使其尽可能地减少破坏性。例如,您可以将 proto2 和 proto3 定义导入基于 editions 的定义文件,反之亦然
// file myproject/foo.proto
syntax = "proto2";
enum Employment {
EMPLOYMENT_UNSPECIFIED = 0;
EMPLOYMENT_FULLTIME = 1;
EMPLOYMENT_PARTTIME = 2;
}
// file myproject/edition.proto
edition = "2023";
import "myproject/foo.proto";
虽然从 proto2 或 proto3 迁移到 editions 时生成的代码会发生变化,但线格式不会。您仍然可以使用 editions 语法的 proto 定义来访问 proto2 和 proto3 数据文件或文件流。
语法变更
与 proto2 和 proto3 相比,Editions 在语法上有一些变化。
语法描述。您不再使用 syntax
元素,而是使用 edition
元素
syntax = "proto2";
syntax = "proto3";
edition = "2028";
保留名称。在保留字段名和枚举值名称时,您不再需要将它们放在引号中
reserved foo, bar;
组语法。 proto2 中可用的组语法在 editions 中被移除。组使用的特殊线格式仍然可以通过使用 DELIMITED
消息编码来实现。
Required 标签。仅在 proto2 中可用的 required
标签在 editions 中不可用。底层功能仍然可以通过使用 features.field_presence=LEGACY_REQUIRED
来实现。