2016-12-13 21:08:37 +03:00
|
|
|
# [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?
|
|
|
|
|
|
|
|
* the good old ./generate.sh bash script
|
|
|
|
* go:generate
|
|
|
|
* make
|
2018-09-12 21:29:07 +03:00
|
|
|
* protobuf + [protoc-gen-gotemplate](https://moul.io/protoc-gen-gotemplate)
|
2016-12-13 21:08:37 +03:00
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
# 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`
|
|
|
|
|
|
|
|
```protobuf
|
|
|
|
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`
|
|
|
|
|
|
|
|
```go
|
|
|
|
package sessionsvc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"golang.org/x/net/context"
|
2018-09-12 21:29:07 +03:00
|
|
|
pb "moul.io/protoc-gen-gotemplate/examples/go-kit/services/session/gen/pb"
|
2016-12-13 21:08:37 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
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`
|
|
|
|
|
|
|
|
```go
|
|
|
|
// 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"
|
2018-09-12 21:29:07 +03:00
|
|
|
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"
|
2016-12-13 21:08:37 +03:00
|
|
|
)
|
|
|
|
```
|
|
|
|
|
|
|
|
```go
|
|
|
|
// 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"
|
2018-09-12 21:29:07 +03:00
|
|
|
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"
|
2016-12-13 21:08:37 +03:00
|
|
|
)
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
##### example: `{{.File.Package}}/gen/transports/http/http.go.tmpl`
|
|
|
|
|
|
|
|
```go
|
|
|
|
// 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}}
|
|
|
|
```
|
|
|
|
|
|
|
|
```go
|
|
|
|
// 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`
|
|
|
|
|
|
|
|
```go
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```go
|
|
|
|
// 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](assets/session-wc.png)
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
![right fit](assets/wc.png)
|
|
|
|
|
|
|
|
## 3 services
|
|
|
|
## 6 methods
|
|
|
|
## 149 custom lines
|
|
|
|
## 1429 generated lines
|
|
|
|
## business focus
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
# generation usages
|
|
|
|
|
2018-09-12 21:29:07 +03:00
|
|
|
* go-kit boilerplate (see [examples/go-kit](https://moul.io/protoc-gen-gotemplate/tree/master/examples/go-kit))
|
2016-12-13 21:08:37 +03:00
|
|
|
* 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?
|
|
|
|
|
2018-09-12 21:29:07 +03:00
|
|
|
### moul.io/protoc-gen-gotemplate
|
2016-12-13 21:08:37 +03:00
|
|
|
### @moul
|