Search code examples
goprotocol-buffersrpctwirp

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!


Solution

  • 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