如何使用 Go 的类型别名让自己的模型与 protobufs 一起工作?

How to use Go's type alias to make own models work with protobufs?

我有一些 REST API,我的模型定义为 Go 结构。

type User struct {
  FirstName string
  LastName  string
}

然后我就有了获取数据的数据库方法。

GetUserByID(id int) (*User, error)

现在我想用 https://github.com/twitchtv/twirp 替换我的 REST API 。

因此我开始在 .proto 个文件中定义我的模型。

message User {
  string first_name = 2;
  string last_name = 3;
}

现在我有两种 User 类型。我们称它们为 nativeproto 类型。

我还在我的 .proto 文件中定义了一个服务,returns 一个用户到前端。

service Users {
  rpc GetUser(Id) returns (User);
}

这会生成一个我必须填写的界面。

func (s *Server) GetUser(context.Context, id) (*User, error) {
  // i'd like to reuse my existing database methods
  u, err := db.GetUserByID(id)
  // handle error
  // do more stuff
  return u, nil
}

不幸的是,这不起作用。我的数据库 returns 一个 native 用户,但界面需要一个 proto 用户。

有没有简单的方法让它工作?也许使用 type aliases?

非常感谢!

解决问题的一种方法是手动进行转换。

type User struct {
    FirstName string
    LastName string
}

type protoUser struct {
    firstName string
    lastName string
}

func main() {
    u := db() // Retrieve a user from a mocked db

    fmt.Println("Before:")
    fmt.Printf("%#v\n", *u) // What db returns (*protoUser)
    fmt.Println("After:")
    fmt.Printf("%#v\n", u.AsUser()) // What conversion returns (User)
}

// Mocked db that returns pointer to protoUser
func db() *protoUser {
    pu := protoUser{"John", "Dough"}
    return &pu
}

// Conversion method (converts protoUser into a User)
func (pu *protoUser) AsUser() User {
    return User{pu.firstName, pu.lastName}
}

The key part is the AsUser method on the protoUser struct.
There we simply write our custom logic for converting a protoUser into a User type we want to be working with.

Working Example

如评论区@Peter所述

我看过一个项目,该项目使用自定义 Convert 函数。它通过 json.Unmarshal 将 Protobuf 转换为本地结构,不确定性能如何,但这是一个可行的方法。

预览代码 PLAYGROUND

// Convert converts the in struct to out struct via `json.Unmarshal`
func Convert(in interface{}, out interface{}) error {
    j, err := json.Marshal(in)
    if err != nil {
        return err
    }
    err = json.Unmarshal(j, &out)
    if err != nil {
        return err
    }
    return nil
}

func main() {
    // Converts the protobuf struct to local struct via json.Unmarshal
    var localUser User
    if err := convert(protoUser, &localUser); err != nil {
        panic(err)
    }
}

输出

Before:
main.ProtoUser{FirstName:"John", LastName:"Dough"}
After:
main.User{FirstName:"John", LastName:"Dough"}

Program exited.