跳到主要内容

可选字段

Protobufs 的一个常见问题是零值(nil)的表达方式:零值的元字段无法编码为二进制的表达方式, 这意味着应用程序无法区分零和没有设置字段值。

为解决这个问题,Protobuf 项目支持一些成为 “包装器类型(wrapper types)” 的 Well-Known 类型

例如 bool 的包装器类型称为 google.protobuf.BoolValue 并被 定义为:

ent/proto/entpb/entpb.proto
// Wrapper message for `bool`.
//
// The JSON representation for `BoolValue` is JSON `true` and `false`.
message BoolValue {
// The bool value.
bool value = 1;
}

entproto 生成 Protobuf 消息定义时,它使用这些包装器类型来表达“可选的(Optional)” ent 字段。

让我们看一下实际操作,修改 ent 模式来包含一个可选字段:

ent/schema/user.go
// Fields of the 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; // <-- this is new

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"),
}
}