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

2.1k Views Asked by At

I've got some REST API with my models defined as Go structs.

type User struct {
  FirstName string
  LastName  string
}

Then I've got my database methods for getting data.

GetUserByID(id int) (*User, error)

Now I'd like to replace my REST API with https://github.com/twitchtv/twirp .

Therefore I started defining my models inside .proto files.

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

Now I've got two User types. Let's call them the native and the proto type.

I've also got a service defined in my .proto file which returns a user to the frontend.

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

This generates an interface that I have to fill in.

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
}

Unfortunately this does not work. My database returns a native User but the interface requires a proto user.

Is there an easy way to make it work? Maybe using type aliases?

Thanks a lot!

2

There are 2 best solutions below

0
On BEST ANSWER

One way you can solve your problem is by doing the conversion manually.

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

0
On

As @Peter mentioned in the comment section.

I've seen a project which made it with a custom Convert function. It converts the Protobuf to local struct via json.Unmarshal, not sure how's the performance but it's a way to go.

Preview Code 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)
    }
}

Output

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

Program exited.