Protobuf 是一种 IDL
Protobuf
是一种语言。更准确地说,它是一种接口描述语言(IDL
)。做出这样的区分很重要,因为正如我们将在后面详细讨论的那样,在 Protobuf
中,我们并不编写像编程语言中那样的逻辑代码,而是编写数据模式,这些模式是用于序列化的契约,并在反序列化时得到满足。因此,在解释我们需要遵循的 .proto
文件编写规则以及序列化和反序列化的细节之前,我们需要首先了解什么是 IDL
,以及这种语言的目标是什么。
IDL
是接口描述语言(Interface Description Language)的缩写,顾名思义,它包含三个部分。第一部分是 接口(Interface),它描述了两个或多个应用程序之间的代码,这些代码隐藏了实现的复杂性。因此,我们不需要对应用程序运行的硬件、操作系统以及编写它们的编程语言做出任何假设。这个接口的设计是硬件、操作系统和编程语言无关的。这对于 Protobuf
和其他许多序列化数据模式非常重要,因为它允许开发者一次编写代码,然后可以跨多个项目使用。
第二部分是 描述(Description),它建立在接口的基础上。我们的接口描述了两个应用程序可以预期接收什么,预期向对方发送什么。这包括描述一些类型及其属性,这些类型之间的关系,以及这些类型如何被序列化和反序列化。为了更直观地理解这一点,来看一个 Protobuf
示例。如果我们想创建一个名为 Account
的类型,它包含 ID、用户名和账户的权限,我们可以编写如下内容:
syntax = "proto3";
enum AccountRight {
ACCOUNT_RIGHT_UNSPECIFIED = 0;
ACCOUNT_RIGHT_READ = 1;
ACCOUNT_RIGHT_READ_WRITE = 2;
ACCOUNT_RIGHT_ADMIN = 3;
}
message Account {
uint64 id = 1;
string username = 2;
AccountRight right = 3;
}
如果我们忽略一些在此阶段不重要的细节,可以看到我们定义了以下内容:
-
一个枚举列出了所有可能的权限,并添加了一个额外的角色
ACCOUNT_RIGHT_UNSPECIFIED
; -
一个消息(相当于类或结构体)列出了
Account
类型应具有的三个属性。
再次强调,不深入细节,代码是可读的,并且 Account
与 AccountRight
之间的关系也很容易理解。
最后,第三部分是 语言(Language)。这是在说,像所有语言一样——无论是计算机语言还是其他语言——我们需要遵循规则,以便其他人或编译器能够理解我们的意图。在 Protobuf 中,我们编写代码以满足编译器(protoc
),然后它会为我们做所有繁重的工作。它会读取我们的代码,并生成我们所需语言的代码,然后我们的用户代码将与生成的代码进行交互。下面是我们之前定义的 Account
类型在 Go 语言中的简化输出:
type AccountRight int32
const (
AccountRight_ACCOUNT_RIGHT_UNSPECIFIED AccountRight = 0
AccountRight_ACCOUNT_RIGHT_READ AccountRight = 1
AccountRight_ACCOUNT_RIGHT_READ_WRITE AccountRight = 2
AccountRight_ACCOUNT_RIGHT_ADMIN AccountRight = 3
)
type Account struct {
Id uint64 `protobuf:"varint,1,…"`
Username string `protobuf:"bytes,2,…"`
Right AccountRight `protobuf:"varint,3,…"`
}
在这段代码中,有几个重要的点需要注意。我们将代码拆解如下:
type AccountRight int32
const (
AccountRight_ACCOUNT_RIGHT_UNSPECIFIED AccountRight = 0
AccountRight_ACCOUNT_RIGHT_READ AccountRight = 1
AccountRight_ACCOUNT_RIGHT_READ_WRITE AccountRight = 2
AccountRight_ACCOUNT_RIGHT_ADMIN AccountRight = 3
)
我们的 AccountRight
枚举被定义为类型为 int32
的常量。每个枚举值的名称以枚举名称为前缀,每个常量都有一个与 Protobuf
代码中等号后面设置的值相对应的值。这些值被称为 字段标签(field tags),我们将在本章后面介绍。
接下来,看看下面的代码:
type Account struct {
Id uint64 `protobuf:"varint,1,…"`
Username string `protobuf:"bytes,2,…"`
Right AccountRight `protobuf:"varint,3,…"`
}
这里,我们的 Account
消息被转译为一个结构体,包含 Id
、Username
和 Right
导出的字段。每个字段的类型从 Protobuf
类型转换为 Go 类型。在我们这里的示例中,Go 类型和 Protobuf
类型的名称是相同的,但需要注意的是,在某些情况下,类型会有所不同。例如,Protobuf
中的 double
类型会转换为 Go 中的 float64
类型。最后,我们看到了字段标签,它们在字段后面以元数据的形式引用。它们的具体含义将在本章稍后解释。
总结一下,IDL
是位于不同应用程序之间的代码,描述了对象及其关系,并遵循某些定义的规则。在 Protobuf
中,这个 IDL
会被读取,并用于生成另一种语言的代码。之后,用户代码将使用生成的代码来序列化和反序列化数据。