可选字段
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"),
}
}