【问题标题】:null value issue in models in grpc-gogrpc-go 模型中的空值问题
【发布时间】:2019-03-13 11:14:48
【问题描述】:

我有两台服务器

  1. 用户服务器:处理所有用户CRUD操作
  2. 产品服务器:处理产品 CRUD 操作并通过 gRPC 调用从用户服务器获取用户信息

    type User struct {
        ID string `json:"id"`
        FirstName      string     `json:"firstName"`
        MiddleName     *string    `json:"middleName,omitempty"`
        LastName       string     `json:"lastName"`
        Email          string     `json:"email"`
        Disabled       bool       `json:"disabled"`
        LastSignedInAt *time.Time `json:"lastSignedInAt,omitempty"`
        Bio            *string    `json:"bio,omitempty"`
        BirthDate      *time.Time `json:"birthDate,omitempty"`
    }
    

    这里有些字段是可选的,因为我使用的是 cockroachDB(extended postgreSQL),所以我将它们保存为指针,以便轻松扫描可变形式的查询结果。

这是我的原型文件:

message User {
    int64 id = 1;
    string firstName = 2;
    string lastName = 3;
    string email = 5;
    bool disabled = 6;
    string lastSignedInAt = 8;
    string bio = 9;
    string birthdate = 10;
    string profession = 14;
}

现在从上面的 proto 文件生成的模型是这样的:"

type User struct {
    Id                   int64                 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
    FirstName            string                `protobuf:"bytes,2,opt,name=firstName,proto3" json:"firstName,omitempty"`
    LastName             string                `protobuf:"bytes,3,opt,name=lastName,proto3" json:"lastName,omitempty"`
    Email                string                `protobuf:"bytes,4,opt,name=email,json=email,proto3" json:"email,omitempty"`
    Disabled             bool                  `protobuf:"varint,6,opt,name=disabled,proto3" json:"disabled,omitempty"`
    LastSignedInAt       string                `protobuf:"bytes,8,opt,name=lastSignedInAt,proto3" json:"lastSignedInAt,omitempty"`
    Bio                  string                `protobuf:"bytes,9,opt,name=bio,proto3" json:"bio,omitempty"`
    Birthdate            string                `protobuf:"bytes,10,opt,name=birthdate,proto3" json:"birthdate,omitempty"`
}

现在的问题是,当我为可选字段使用指针时,它会在没有值的情况下存储 null,但在相反的站点上,gRPC 不会理解 null 值并抛出错误。

我已经尝试将google.protobuf.StringValue 值作为这样的 grpc 类型

google.protobuf.StringValue lastSignedInAt = 8;

这可行,但问题是我必须为处理程序中的每个字段编写条件:

if lastSignedInAt != nil {
    user.LastSignedInAt = &wrappers.StringValue{Value:*lastSignedInAt}
}

解决此问题的最佳方法是什么?我应该更改数据库模型还是 gRPC 模型中的任何更改?

【问题讨论】:

  • 我看到两个user struct有相同的json标签,你有没有试过用json.Marshal和json.Unmarshal来转换两个user struct?
  • 您是否打算通过其他方式将 Proto 复制到 Struct,而不是逐个字段?这可能是最快和最安全的方法。 Protobuf 在其网站上声明它不是通用数据结构,并建议您将其转换为特定领域的表示形式。

标签: go protocol-buffers grpc


【解决方案1】:

正如上面提到的另一个答案,如果协议缓冲区消息具有可以为 nil 的字段,则需要您检查它们。对此您无能为力,除非有一个实用程序包可以做到这一点。

如果您想要超出协议缓冲区生成的默认值的默认值,您必须完全按照您的说明进行操作并检查 nil。

我确实有一些问题:

  • 为什么对第一个用户结构中的“可选”字段使用指针?如果您使用普通的旧字符串,它们将在构造时填充空字符串,并且如果该字段丢失,则不会被 json unmarshal 调用所触及。时间字段也是如此。在这种情况下,字符串(空字符串)的默认值应该是无效的中间名,时间(0001-01-01 00:00:00 +0000 UTC)的默认值是无效的时间戳(可能?)。摆脱指针允许您使用默认值。
  • 对于 proto 结构中的时间戳,您仍然可以使用字符串并检查空字符串。或者您可以使用 google.protobuf.Timestamp 并使用 ptypes 来处理与非原型结构的转换。见https://godoc.org/github.com/golang/protobuf/ptypes#TimestampProto。在这种情况下,您必须检查 nil。

【讨论】:

    【解决方案2】:

    你不能设置空值,而是你可以使用

    oneof Examples {
        Example1 example1 = 1;
        Example2 example2 = 2;
    }
    

    当您使用 oneof 时,您只需设置一个值,您可以设置 example1 或 example2,但您不能同时使用两者。与设置 nil 值相比,这将解决您的问题。

    方法二:

    默认情况下,gRPC 的所有变量都具有初始值,例如:string: ""

    你也可以做的一件事是不要设置 nil 值检查条件,如果你的值是 nil 然后什么都不设置。

    【讨论】:

    • 问题是我得到了 nil;来自数据库,所以我必须为每个可选变量编写 if-else 阶梯。
    • 您必须手动检查您的字段是否可以为空。没有这样的解决方案,您必须按照stackoverflow.com/questions/21227924/… 处理此问题。
    【解决方案3】:

    grpc 不建议使用点字段,如果你坚持使用指针

    一种方法是使用反射来转换它

    func ToGrpcData(in, out interface{}) error {
        inVal := reflect.ValueOf(in)
        if inVal.Kind() == reflect.Ptr {
            inVal = inVal.Elem()
        }
        inTyp := inVal.Type()
    
        outVal := reflect.ValueOf(out)
        if outVal.Kind() != reflect.Ptr {
            return errors.New("out data must be point value")
        }
    
        outVal = outVal.Elem()
        outTyp := outVal.Type()
    
        strWrapperType := reflect.TypeOf(wrappers.StringValue{})
        // range all 'in' fields
        for i := 0; i < inVal.NumField(); i++ {
            fTyp := inTyp.Field(i)
            fVal := inVal.Field(i)
            if fTyp.Type.Kind() == reflect.Ptr {
                switch fTyp.Type.Elem().Kind() {
                case reflect.String: // only implement string in this test
                    oFTyp, ok := outTyp.FieldByName(fTyp.Name)
                    if ok == false {
                        return errors.New("not match field in out value")
                    }
                    if oFTyp.Type.Elem() != strWrapperType {
                        return errors.New("not match field in out value")
                    }
                    if fVal.IsNil() == false {
                        outVal.FieldByName(fTyp.Name).Set(
                            reflect.ValueOf(&wrappers.StringValue{
                                Value: fVal.Elem().String(),
                            }),
                        )
                    }
                }
            } else {
                outVal.FieldByName(fTyp.Name).Set(fVal)
            }
        }
        return nil
    }
    

    用法

    type User struct {
        Name  string
        Value *string
    }
    type ServerUser struct {
        Name  string
        Value *wrappers.StringValue
    }
    usValue := "123"
    u1 := User{
        Name:  "tom",
        Value: &usValue,
    }
    u2 := ServerUser{}
    err := ToGrpcData(&u1, &u2)
    // error process ...
    fmt.Println(u2)
    

    【讨论】:

      猜你喜欢
      • 2021-12-01
      • 2021-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-18
      • 2012-02-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多