zemirco
zemirco

Reputation: 16395

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

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!

Upvotes: 2

Views: 2228

Answers (2)

Yami Odymel
Yami Odymel

Reputation: 1898

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.

Upvotes: 0

Mihailo
Mihailo

Reputation: 4917

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

Upvotes: 1

Related Questions