跳到主要内容

可选字段

Protobuf 的一个常见问题是空值的表示方式:零值原始字段不会被编码到二进制表示中,这意味着应用程序无法区分原始字段的零值和未设置值。

为解决这个问题,Protobuf 项目支持一些称为「包装类型」的知名类型。 例如,bool 的包装类型名为 google.protobuf.BoolValue,其定义如下

ent/proto/entpb/entpb.proto
// `bool` 的包装消息。
//
// `BoolValue` 的 JSON 表示为 JSON 的 `true` 和 `false`。
message BoolValue {
// 布尔值。
bool value = 1;
}

entproto 生成 Protobuf 消息定义时,会使用这些包装类型来表示「可选」的 ent 字段。

让我们通过修改 ent 模式来添加一个可选字段以实际查看效果:

ent/schema/user.go
// User 的字段。
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Unique().
Annotations(
entproto.Field(2),
),
field.String("email_address").
Unique().
Annotations(
entproto.Field(3),
),
field.String("alias").
Optional().
Annotations(entproto.Field(4)),
}
}

重新运行 go generate ./... 后,可以看到 User 的 Protobuf 定义现在变为:

ent/proto/entpb/entpb.proto
message User {
int32 id = 1;

string name = 2;

string email_address = 3;

google.protobuf.StringValue alias = 4; // <-- 这是新增部分

repeated Category administered = 5;
}

生成的服务实现也会利用此字段。请注意 entpb_user_service.go 中的代码:

ent/proto/entpb/entpb_user_service.go
func (svc *UserService) createBuilder(user *User) (*ent.UserCreate, error) {
m := svc.client.User.Create()
if user.GetAlias() != nil {
userAlias := user.GetAlias().GetValue()
m.SetAlias(userAlias)
}
userEmailAddress := user.GetEmailAddress()
m.SetEmailAddress(userEmailAddress)
userName := user.GetName()
m.SetName(userName)
for _, item := range user.GetAdministered() {
administered := int(item.GetId())
m.AddAdministeredIDs(administered)
}
return m, nil
}

要在客户端代码中使用包装类型,可以使用 wrapperspb 包提供的辅助方法来轻松构建这些类型的实例。例如在 cmd/client/main.go 中:

func randomUser() *entpb.User {
return &entpb.User{
Name: fmt.Sprintf("user_%d", rand.Int()),
EmailAddress: fmt.Sprintf("user_%d@example.com", rand.Int()),
Alias: wrapperspb.String("John Doe"),
}
}