2018-09-13 16:38:39 +02:00

5.3 KiB

[fit] Protobuf & Code Generation

2016, by Manfred Touron (@moul)


overview

  • go-kit is an amazing framework to develop strong micro services
  • but it requires a lot of boilerplate code
  • return on experience on go-kit boilerplate code generation

protobuf?

  • limited to exchanges (methods and models)
  • extendable with plugins
  • contract-based
  • universal

code generation?


go-kit

  • protobuf-first, rpc-first service framework in Golang
  • abstract services, endpoints, transports
  • requires a lot of boilerplate code in multiple packages

example: session.proto

syntax = "proto3";
package session;

service SessionService {
  rpc Login(LoginRequest) returns (LoginResponse) {}
}

message LoginRequest {
  string username = 1;
  string password = 2;
}

message LoginResponse {
  string token = 1;
  string err_msg = 2;
}

example: session.go

package sessionsvc

import (
	"fmt"
	"golang.org/x/net/context"
	pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
)

type Service struct{}

func New() pb.SessionServiceServer {
	return &Service{}
}

func (svc *Service) Login(ctx context.Context, in *pb.LoginRequest) (*pb.LoginResponse, error) {
    // custon code here
	return nil, fmt.Errorf("not implemented")
}

example: {{.File.Package}}/gen/transports/http/http.go.tmpl
// source: templates/{{.File.Package}}/gen/transports/http/http.go.tmpl
package {{.File.Package}}_httptransport
import (
    gokit_endpoint "github.com/go-kit/kit/endpoint"
	httptransport "github.com/go-kit/kit/transport/http"
	endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/{{.File.Package}}/gen/endpoints"
	pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/{{.File.Package}}/gen/pb"
)
// result: services/user/gen/transports/http/http.go
package user_httptransport
import (
	gokit_endpoint "github.com/go-kit/kit/endpoint"
	httptransport "github.com/go-kit/kit/transport/http"
	endpoints "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/endpoints"
	pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/user/gen/pb"
)

example: {{.File.Package}}/gen/transports/http/http.go.tmpl
// source: templates/{{.File.Package}}/gen/transports/http/http.go.tmpl
{{range .Service.Method}}
func Make{{.Name}}Handler(ctx context.Context, svc pb.{{$file.Package | title}}ServiceServer, endpoint gokit_endpoint.Endpoint) *httptransport.Server {
	return httptransport.NewServer(
		ctx,
		endpoint,
		decode{{.Name}}Request,
		encode{{.Name}}Response,
                []httptransport.ServerOption{}...,
	)
}
{{end}}
// result: services/user/gen/transports/http/http.go
func MakeGetUserHandler(ctx context.Context, svc pb.UserServiceServer, endpoint gokit_endpoint.Endpoint) *httptransport.Server {
	return httptransport.NewServer(
		ctx,
		endpoint,
		decodeGetUserRequest,
		encodeGetUserResponse,
		[]httptransport.ServerOption{}...,
	)
}

example: {{.File.Package}}/gen/transports/http/http.go.tmpl
// source: templates/{{.File.Package}}/gen/transports/http/http.go.tmpl
func RegisterHandlers(ctx context.Context, svc pb.{{$file.Package | title}}ServiceServer, mux *http.ServeMux, endpoints endpoints.Endpoints) error {
	{{range .Service.Method}}
        log.Println("new HTTP endpoint: \"/{{.Name}}\" (service={{$file.Package | title}})")
	mux.Handle("/{{.Name}}", Make{{.Name}}Handler(ctx, svc, endpoints.{{.Name}}Endpoint))
	{{end}}
	return nil
}
// result: services/user/gen/transports/http/http.go
func RegisterHandlers(ctx context.Context, svc pb.UserServiceServer, mux *http.ServeMux, endpoints endpoints.Endpoints) error {

	log.Println("new HTTP endpoint: \"/CreateUser\" (service=User)")
	mux.Handle("/CreateUser", MakeCreateUserHandler(ctx, svc, endpoints.CreateUserEndpoint))

	log.Println("new HTTP endpoint: \"/GetUser\" (service=User)")
	mux.Handle("/GetUser", MakeGetUserHandler(ctx, svc, endpoints.GetUserEndpoint))

	return nil
}

protoc --gogo_out=plugins=grpc:. ./services/*/*.proto


protoc --gotemplate_out=template_dir=./templates:services ./services/*/*.proto


fit


right fit

3 services

6 methods

149 custom lines

1429 generated lines

business focus


generation usages

  • go-kit boilerplate (see examples/go-kit)
  • k8s configuration
  • Dockerfile
  • documentation
  • unit-tests
  • fun

pros

  • small custom codebase
  • templates shipped with code
  • hardly typed, no reflects
  • genericity
  • contrat terms (protobuf) respected
  • not limited to a language

cons

  • the author needs to write its own templates
  • sometimes difficult to generate valid code
  • not enough helpers around the code generation yet

improvement ideas

  • Support protobufs extensions (i.e, annotations.probo)
  • Generate one file from multiple services
  • Add more helpers around the code generation

conclusion

  • Useful to keep everything standard
  • The awesomeness of go-kit without the hassle of writing boilerplate code
  • Always up-to-date with the contracts

questions?

moul.io/protoc-gen-gotemplate

@moul