Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Protobuf-First APIs: gRPC & Co.

Protobuf-First APIs: gRPC & Co.

This talk about how to build gRPC and REST API using Protocol buffers and then generate clients for your API.

2fca4480ba42eeedd5cd35e6249f3aa6?s=128

Evgeny Khabarov

September 18, 2019
Tweet

Transcript

  1. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 1/46 Protobuf-First APIs:

    gRPC & Co. Protobuf-First APIs: gRPC & Co. Sept 18, 2019 Sept 18, 2019 Evgeny Khabarov Evgeny Khabarov Bold Commerce, Winnipeg, Canada Bold Commerce, Winnipeg, Canada
  2. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 2/46 $ whoami

    $ whoami Evgeny Khabarov Evgeny Khabarov Senior Golang developer @ Bold Commerce Senior Golang developer @ Bold Commerce @eekhabarov @eekhabarov (https://twitter.com/eekhabarov) (https://twitter.com/eekhabarov) on Twitter on Twitter @ekhabarov @ekhabarov (https://github.com/ekhabarov) (https://github.com/ekhabarov) on Github/Gophers Slack on Github/Gophers Slack
  3. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 3/46 Gophers Slack

    Gophers Slack https://gophers.slack.com/ https://gophers.slack.com/ (https://gophers.slack.com/) (https://gophers.slack.com/) => => #Winnipeg #Winnipeg channel. channel. https://invite.slack.golangbridge.org/ https://invite.slack.golangbridge.org/ (https://invite.slack.golangbridge.org) (https://invite.slack.golangbridge.org) - invites. - invites. @golangwpg @golangwpg (https://twitter.com/golangwpg) (https://twitter.com/golangwpg) on Twitter on Twitter
  4. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 4/46 What is

    this talk about? What is this talk about? How to use protobuf for building gRPC and REST APIs. How to use protobuf for building gRPC and REST APIs. How to save your own Time. How to save your own Time.
  5. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 5/46 Agenda Agenda

    Briefly about Bold Commerce Briefly about Bold Commerce How we're building APIs How we're building APIs gRPC & Protobuf gRPC & Protobuf gRPC-Gateway and other plugins gRPC-Gateway and other plugins
  6. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 6/46 Who we

    are and what we're doing? Who we are and what we're doing? Bold Commerce, Winnipeg, Canada. Bold Commerce, Winnipeg, Canada. Building apps for e-commerce platforms Shopify and BigCommerce. Building apps for e-commerce platforms Shopify and BigCommerce. At the moment we have 20+ apps, written mostly in PHP. At the moment we have 20+ apps, written mostly in PHP.
  7. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 7/46 Everything was

    fine... Everything was fine... Apps use platform directly. Apps use platform directly. All integrations with Shopify were made on app own way. All integrations with Shopify were made on app own way. Different apps work with different object. Different apps work with different object.
  8. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 8/46 ...before integration

    with second platform ...before integration with second platform
  9. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 9/46 Issues we've

    faced Issues we've faced A lot of integration work spread across apps. A lot of integration work spread across apps. A lot of duplicated work. A lot of duplicated work. Inconsistency: different apps need different parts of product objects. Inconsistency: different apps need different parts of product objects.
  10. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 10/46 Platform API

    Platform API
  11. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 11/46 Platform API

    version 1 Platform API version 1
  12. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 12/46 What's inside?

    What's inside? Microservice architecture with API Gateway. Microservice architecture with API Gateway. gRPC endpoints for interservice communication. gRPC endpoints for interservice communication. HTTP endpoints for PHP apps. HTTP endpoints for PHP apps. go-kit go-kit
  13. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 13/46 go-kit go-kit

    gokit.io gokit.io (https://gokit.io) (https://gokit.io) Collection of packages that help you build microservices. Collection of packages that help you build microservices. Supports different transports: HTTP, gRPC, Thrift, and net/rpc. Supports different transports: HTTP, gRPC, Thrift, and net/rpc. Mutli-layered structure. Mutli-layered structure.
  14. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 14/46 HTTP endpoint

    with standard lib. HTTP endpoint with standard lib. func main() { func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello Winnipeg!") fmt.Fprintf(w, "Hello Winnipeg!") }) }) http.ListenAndServe(":80", nil) http.ListenAndServe(":80", nil) } }
  15. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 15/46 go-kit endpoint.

    go-kit endpoint. type printRequest struct { type printRequest struct { // request parameters // request parameters } } type printResponse struct { type printResponse struct { V string `json:"v"` V string `json:"v"` Err string `json:"err,omitempty"` Err string `json:"err,omitempty"` } } func makePrintEndpoint() endpoint.Endpoint { func makePrintEndpoint() endpoint.Endpoint { return func(_ context.Context, request interface{}) (interface{}, error) { return func(_ context.Context, request interface{}) (interface{}, error) { req := request.(printRequest) req := request.(printRequest) return printResponse{"Hello Winnipeg!", ""}, nil return printResponse{"Hello Winnipeg!", ""}, nil } } } } func decodePrintRequest(_ context.Context, r *http.Request) (interface{}, error) { func decodePrintRequest(_ context.Context, r *http.Request) (interface{}, error) { var request printRequest var request printRequest if err := json.NewDecoder(r.Body).Decode(&request); err != nil { if err := json.NewDecoder(r.Body).Decode(&request); err != nil { return nil, err return nil, err } } return request, nil return request, nil } }
  16. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 16/46 go-kit endpoint

    (cont.) go-kit endpoint (cont.) func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { return json.NewEncoder(w).Encode(response) return json.NewEncoder(w).Encode(response) } } func main() { func main() { printHandler := httptransport.NewServer( printHandler := httptransport.NewServer( makePrintEndpoint(), makePrintEndpoint(), decodePrintRequest, decodePrintRequest, encodeResponse, encodeResponse, ) ) http.Handle("/", printHandler) http.Handle("/", printHandler) http.ListenAndServe(":8080", nil) http.ListenAndServe(":8080", nil) } }
  17. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 17/46 Why it's

    not suitable for us? Why it's not suitable for us? Too flexible. Too flexible. Too verbose. Too verbose. In order to add one HTTP handler it's necessary change a lot of code. In order to add one HTTP handler it's necessary change a lot of code. Encoders, decoders and endpoints are mostly copy-pasted. Encoders, decoders and endpoints are mostly copy-pasted.
  18. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 18/46 Issue #

    1 Issue # 1 Development speed. Development speed. Error-prone approach. Error-prone approach. gRPC/HTTP gRPC/HTTP
  19. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 19/46 Apps... Apps...

  20. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 20/46 Issue #

    2 Issue # 2
  21. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 21/46 Issue #

    2: causes Issue # 2: causes Clients quickly become outdated. Clients quickly become outdated. API changes are multiplied by number of clients. API changes are multiplied by number of clients. In most cases, when I change an API I don't think about clients, and just forgot update In most cases, when I change an API I don't think about clients, and just forgot update them. them.
  22. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 22/46 gRPC gRPC

  23. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 23/46 What is

    gRPC? What is gRPC? A high performance, open-source universal RPC framework. A high performance, open-source universal RPC framework. Platform and language independent. Platform and language independent. Uses Protocol buffers ( Uses Protocol buffers (protobuf protobuf) for service definition. ) for service definition. grpc.io grpc.io (https://grpc.io) (https://grpc.io)
  24. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 24/46 Protocol buffers

    (protobuf) Protocol buffers (protobuf) Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. for serializing structured data – think XML, but smaller, faster, and simpler. developers.google.com/protocol-buffers developers.google.com/protocol-buffers (https://developers.google.com/protocol-buffers) (https://developers.google.com/protocol-buffers)
  25. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 25/46 Service described

    with protobuf Service described with protobuf // service.proto // service.proto syntax = "proto3"; syntax = "proto3"; package svc.greeter; package svc.greeter; option go_package = "pb"; option go_package = "pb"; import "protobuf/gogoproto/gogo.proto"; import "protobuf/gogoproto/gogo.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto"; service Greeter { service Greeter { rpc SayHello (HelloRequest) returns (HelloResponse); rpc SayHello (HelloRequest) returns (HelloResponse); } } message HelloRequest { message HelloRequest { string name = 1; string name = 1; } } message HelloResponse { message HelloResponse { string message = 1; string message = 1; google.protobuf.Timestamp response_time = 2 [(gogoproto.stdtime) = true]; google.protobuf.Timestamp response_time = 2 [(gogoproto.stdtime) = true]; } }
  26. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 26/46 Protobuf compiler

    (protoc) Protobuf compiler (protoc) Takes Takes .proto .proto files and generates classes and structures for serializing data. files and generates classes and structures for serializing data. Extendable with plugins. Extendable with plugins.
  27. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 27/46 protoc command

    & plugins protoc command & plugins Without plugins protoc is able to generate c++ code only. Other languages are supported Without plugins protoc is able to generate c++ code only. Other languages are supported by plugins. by plugins. Plugin is a binary with a name Plugin is a binary with a name protoc-gen-<plugin_name> protoc-gen-<plugin_name>. . Plugin should be available in $PATH Plugin should be available in $PATH % ls $GOBIN | grep protoc-gen % ls $GOBIN | grep protoc-gen protoc-gen-go protoc-gen-go protoc-gen-gogo protoc-gen-gogo protoc-gen-gogofaster protoc-gen-gogofaster protoc-gen-grpc-gateway protoc-gen-grpc-gateway protoc-gen-swagger protoc-gen-swagger protoc-gen-twirp protoc-gen-twirp protoc \ protoc \ --proto_path=<import_dirs>:. \ --proto_path=<import_dirs>:. \ --<plugin_name>_out=<plugin_options>:<output> \ --<plugin_name>_out=<plugin_options>:<output> \ --<plugin_name_2>_out=<plugin_options_2>:<output_2> \ --<plugin_name_2>_out=<plugin_options_2>:<output_2> \ /path/to/service.proto /path/to/service.proto
  28. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 28/46 Plugins for

    Golang Plugins for Golang
  29. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 29/46 Run protoc

    Run protoc protoc \ protoc \ --proto_path=${GOPATH}/src/github.com/gogo:. \ --proto_path=${GOPATH}/src/github.com/gogo:. \ --gogofaster_out=plugins=grpc:. \ --gogofaster_out=plugins=grpc:. \ service.proto service.proto
  30. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 30/46 Generated code:

    service.pb.go Generated code: service.pb.go package pb package pb type GreeterServer interface { type GreeterServer interface { SayHello(context.Context, *HelloRequest) (*HelloResponse, error) SayHello(context.Context, *HelloRequest) (*HelloResponse, error) } } type GreeterClient interface { type GreeterClient interface { SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) } } type HelloRequest struct { type HelloRequest struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } } type HelloResponse struct { type HelloResponse struct { Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` ResponseTime *time.Time `protobuf:"bytes,2,opt,name=response_time,json=responseTime,proto3,stdtime" ResponseTime *time.Time `protobuf:"bytes,2,opt,name=response_time,json=responseTime,proto3,stdtime" } } It also contains a bunch of code for serialization. It also contains a bunch of code for serialization.
  31. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 31/46 Implementation Implementation

    type server struct{} type server struct{} func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) { return &pb.HelloResponse{Message: "Hello " + req.Name} return &pb.HelloResponse{Message: "Hello " + req.Name} } } func main() { func main() { lis, err := net.Listen("tcp", ":5050") lis, err := net.Listen("tcp", ":5050") if err != nil { if err != nil { log.Fatal(err) log.Fatal(err) } } s := grpc.NewServer() s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) pb.RegisterGreeterServer(s, &server{}) s.Server(lis) s.Server(lis) } }
  32. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 32/46 OK, how

    about REST API? OK, how about REST API?
  33. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 33/46 It's here

    It's here protoc \ protoc \ --proto_path=${GOPATH}/src/github.com/gogo:. \ --proto_path=${GOPATH}/src/github.com/gogo:. \ --gogofaster_out=plugins=grpc:. \ --gogofaster_out=plugins=grpc:. \ --grpc-gateway_out=grpc_api_configuration=router.yaml \ << here --grpc-gateway_out=grpc_api_configuration=router.yaml \ << here service.proto service.proto # router.yaml # router.yaml type: google.api.Service type: google.api.Service config_version: 3 config_version: 3 http: http: rules: rules: - selector: svc.greeter.Greeter.SayHello - selector: svc.greeter.Greeter.SayHello get: /hello/{name} get: /hello/{name} Run protoc again. Run protoc again. service.pb.gw.go will be generated. service.pb.gw.go will be generated.
  34. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 34/46 Generated code:

    service.pb.gw.go Generated code: service.pb.gw.go Contains HTTP reverse proxy. Contains HTTP reverse proxy. Calls gRPC service directly underthehood. Calls gRPC service directly underthehood.
  35. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 35/46 Change main.go

    a bit Change main.go a bit func main() { func main() { lis, err := net.Listen("tcp", ":5050") lis, err := net.Listen("tcp", ":5050") if err != nil { if err != nil { log.Fatal(err) log.Fatal(err) } } s := grpc.NewServer() s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) pb.RegisterGreeterServer(s, &server{}) go s.Server(lis) go s.Server(lis) handler := runtime.NewServeMux() handler := runtime.NewServeMux() pb.RegisterGreeterHandlerFromEndpoint(ctx, handler, lis.Addr().String()) pb.RegisterGreeterHandlerFromEndpoint(ctx, handler, lis.Addr().String()) http.HandleFunc("/", handler) http.HandleFunc("/", handler) http.ListenAndServe(":80", nil) http.ListenAndServe(":80", nil) } }
  36. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 36/46 grpc-gateway grpc-gateway

    It's a It's a plugin plugin of the Google protocol buffers compiler protoc. It reads protobuf service of the Google protocol buffers compiler protoc. It reads protobuf service definitions and definitions and generates generates a reverse-proxy server which translates a RESTful HTTP API into a reverse-proxy server which translates a RESTful HTTP API into gRPC. gRPC. github.com/grpc-ecosystem/grpc-gateway github.com/grpc-ecosystem/grpc-gateway (https://github.com/grpc-ecosystem/grpc-gateway) (https://github.com/grpc-ecosystem/grpc-gateway)
  37. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 37/46 The Idea

    The Idea
  38. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 38/46 Swagger Swagger

    Machine-readable REST API definition in JSON or YAML format. Machine-readable REST API definition in JSON or YAML format.
  39. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 39/46 How to

    get Swagger definition? How to get Swagger definition? User one more protoc plugin. User one more protoc plugin. protoc \ protoc \ --proto_path=${GOPATH}/src/github.com/gogo:. \ --proto_path=${GOPATH}/src/github.com/gogo:. \ --gogofaster_out=plugins=grpc:. \ --gogofaster_out=plugins=grpc:. \ --grpc-gateway_out=grpc_api_configuration=router.yaml \ --grpc-gateway_out=grpc_api_configuration=router.yaml \ --swagger_out=grpc_api_configuration=router.yaml:. \ << here --swagger_out=grpc_api_configuration=router.yaml:. \ << here service.proto service.proto
  40. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 40/46 swagger.json swagger.json

    { { "swagger": "2.0", "swagger": "2.0", "info": { "title": "service.proto", "version": "1.0.0" }, "info": { "title": "service.proto", "version": "1.0.0" }, "schemes": [ "http", "https" ], "schemes": [ "http", "https" ], "consumes": [ "application/json" ], "consumes": [ "application/json" ], "produces": [ "application/json" ], "produces": [ "application/json" ], "paths": { "paths": { "/hello/{name}": { "/hello/{name}": { "get": { "get": { "operationId": "SayHello", "operationId": "SayHello", "responses": { "responses": { "200": { "200": { "description": "A successful response.", "description": "A successful response.", "schema": { "$ref": "#/definitions/greeterHelloResponse" } } "schema": { "$ref": "#/definitions/greeterHelloResponse" } } }, }, "parameters": [ { "name": "name", "in": "path", "required": true, "type": "string" } ] "parameters": [ { "name": "name", "in": "path", "required": true, "type": "string" } ] } } }, } } }, "definitions": { "definitions": { "greeterHelloResponse": { "greeterHelloResponse": { "type": "object", "type": "object", "properties": { "properties": { "message": { "type": "string" }, "message": { "type": "string" }, "response_time": { "type": "string", "format": "date-time" } "response_time": { "type": "string", "format": "date-time" } } } } } } } } }
  41. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 41/46 Build PHP

    client Build PHP client docker run --rm \ docker run --rm \ -v /path/to/config:/config \ -v /path/to/config:/config \ -v /path/to/input:/input \ -v /path/to/input:/input \ -v /path/to/output:/output \ -v /path/to/output:/output \ apaleo/swagger-codegen generate \ apaleo/swagger-codegen generate \ --config /config/php-config.json \ --config /config/php-config.json \ -i /input/service.swagger.json \ -i /input/service.swagger.json \ -l php \ -l php \ -o /output -o /output Add this command to CI and forgot about it. Add this command to CI and forgot about it.
  42. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 42/46 API clients

    & documentation API clients & documentation github.com/swagger-api/swagger-codegen github.com/swagger-api/swagger-codegen (https://github.com/swagger-api/swagger-codegen) (https://github.com/swagger-api/swagger-codegen) github.com/Mermade/widdershins github.com/Mermade/widdershins (https://github.com/Mermade/widdershins) (https://github.com/Mermade/widdershins)
  43. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 43/46 Pros &

    Cons Pros & Cons Pros Pros: : Declarative development. Declarative development. Fast implementation, when you have appropriate toolset. Fast implementation, when you have appropriate toolset. Less code. Less code. All-in-one: API, docs, clients. All-in-one: API, docs, clients. Extendable with plugins. Extendable with plugins. Cons Cons: : gRPC is hard to debug in compare with REST. gRPC is hard to debug in compare with REST. Generated code is hard to fix. Generated code is hard to fix. Toolset: different versions. Toolset: different versions. Toolset: different plugins which do almost same: go, gogo, gogofast, gogofaster. Toolset: different plugins which do almost same: go, gogo, gogofast, gogofaster.
  44. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 44/46 Questions? Questions?

    @eekhabarov @eekhabarov (https://twitter.com/eekhabarov) (https://twitter.com/eekhabarov) on Twitter on Twitter @ekhabarov @ekhabarov (https://github.com/ekhabarov) (https://github.com/ekhabarov) on Github/GopherSlack on Github/GopherSlack
  45. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 45/46 Thank you

    Thank you Evgeny Khabarov Evgeny Khabarov Bold Commerce, Winnipeg, Canada Bold Commerce, Winnipeg, Canada
  46. 9/19/2019 Protobuf-First APIs: gRPC & Co. 127.0.0.1:3999/protobuf/protobuf.slide#27 46/46