Go + microservices = Go kit

Go + microservices = Go kit

Microservices are hard, but Go and Go kit can make them easier.

Eb68eb9603a0e8822eda69e7ca721866?s=128

Peter Bourgon

March 29, 2017
Tweet

Transcript

  1. 2.

    Go is a good language
 for microservices I want to

    convince you that and maybe Go kit will help
  2. 4.

    History Date: Sun, 23 Sep 2007 23:33:41 -0700
 From: "Robert

    Griesemer" <gri@google.com>
 To: "Rob 'Commander' Pike" <r@google.com>, ken@google.com
 Subject: prog lang discussion
 ...
 *** General:
 Starting point: C, fix some obvious flaws, remove crud, add a few missing features
 - no includes, instead: import
 - no macros (do we need something instead?)
 - ideally only one file instead of a .h and .c file, module interface 
 should be extracted automatically
 - statements: like in C, though should fix 'switch' statement
 - expressions: like in C, though with caveats (do we need ',' expressions?)
 - essentially strongly typed, but probably w/ support for runtime types
 - want arrays with bounds checking on always (except perhaps in 'unsafe mode'-see section on GC)
 - mechanism to hook up GC (I think that most code can live w/ GC, but for a true systems
 programming language there should be mode w/ full control over memory allocation)
 - support for interfaces (differentiate between concrete, or implementation types, and abstract,
 or interface types)
 - support for nested and anonymous functions/closures (don't pay if not used)
 - a simple compiler should be able to generate decent code
 - the various language mechanisms should result in predictable code
 ...
  3. 5.

    Go properties • Statically typed • Compiled — fast! •

    Native binaries • Garbage collected • Looks like C • Big standard library • Baked-in concurrency
  4. 6.

    Go IMO • A breath of fresh air from “kitchen

    sink” languages — cf. Scala • Simple, orthogonal features that aren't surprising — cf. Node • Efficient by default — cf. Python, Ruby • Predictable runtime behavior — cf. JVM languages • Familiar heritage, syntax, and paradigm — cf. Haskell, Elixir
  5. 10.
  6. 11.
  7. 12.
  8. 14.
  9. 16.

    Problems solved • Team is too large to work effectively

    on shared codebase • Teams are blocked on other teams, can't make progress • Communication overhead becomes gigantic • Product velocity stalled
  10. 17.

    Problems caused • Need well-defined business domains for stable APIs

    • No more shared DB — distributed transactions? • Testing becomes really hard • Require dev/ops culture: devs deploy & operate their work • Job (service) scheduling — manually works, for a while...
  11. 18.

    Problems caused • Addressability i.e. service discovery • Monitoring and

    instrumentation — tail -f? Nagios & New Relic? Ha! • Distributed tracing? • Build pipelines?? • Security???
  12. 19.
  13. 20.
  14. 21.
  15. 22.
  16. 25.

    Initial goals • A standard library for microservices • Something

    like Finagle for Go • Adapters, bindings, etc. to common infrastructure components • Play nice in your existing, heterogeneous infrastructure • Structure to tame the beast of incidental complexity
  17. 26.

    Current goals • Mostly the same • Even less opinionated

    about infrastructure than anticipated • More opinionated about application architecture than anticipated
  18. 27.

    Non-goals • Messaging patterns other than RPC • Requiring specific

    bits of infrastructure or tooling to work • Acting as an all-in service framework • Re-implementing existing, good solutions to problems
  19. 28.

    Comparisons • Micro (Go) — very opinionated, all-in, framework-ish •

    Finagle (Scala) — original inspiration, lower-level than Go kit • Spring Boot (Java) — similar abstractions, far more magical • Tokio (Rust) — explicitly a clone of Finagle, lower-level than Go kit
  20. 29.

    I don’t care if you use Go kit; I do

    care if you use Go I really want to emphasize
  21. 34.

    type basicService struct{} func (s basicService) Sum(a, b int) (int,

    error) { return a + b, nil } func (s basicService) Concat(a, b string) (string, error) { return a + b, nil } Core Business Logic
  22. 37.

    func (s basicService) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path

    { case "/sum": var req struct { A int `json:"a"` B int `json:"b"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return }
  23. 38.

    func (s basicService) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path

    { case "/sum": var req struct { A int `json:"a"` B int `json:"b"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } v, err := s.Sum(req.A, req.B) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }
  24. 39.

    func (s basicService) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path

    { case "/sum": var req struct { A int `json:"a"` B int `json:"b"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } v, err := s.Sum(req.A, req.B) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]int{"v": v})
  25. 41.

    case "/concat": var req struct { A string `json:"a"` B

    string `json:"b"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return }
  26. 42.

    case "/concat": var req struct { A string `json:"a"` B

    string `json:"b"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } v, err := s.Concat(req.A, req.B) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }
  27. 43.

    case "/concat": var req struct { A string `json:"a"` B

    string `json:"b"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } v, err := s.Concat(req.A, req.B) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]string{"v": v})
  28. 45.

    if err := json.NewDecoder(r.Body).Decode(&req); err != nil { code :=

    http.StatusBadRequest log.Printf("%s: %s: %d", r.RemoteAddr, r.URL, code) http.Error(w, err.Error(), code) return } v, err := s.Sum(req.A, req.B) if err != nil { code := http.StatusInternalServerError log.Printf("%s: %s: %d", r.RemoteAddr, r.URL, code) http.Error(w, err.Error(), code) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]int{"v": v}) log.Printf("%s: %s: %d", r.RemoteAddr, r.URL, 200)
  29. 46.

    type basicService struct{} func (s basicService) Sum(a, b int) (v

    int, err error) { defer func() { log.Printf(“Sum(%d,%d)=(%d,%v)”, a, b, v, err) }() return a + b, nil } func (s basicService) Concat(a, b string) (string, error) { defer func() { log.Printf(“Concat(%q,%q)=(%q,%v)”, a, b, v, err) }() return a + b, nil }
  30. 48.

    if err := json.NewDecoder(r.Body).Decode(&req); err != nil { code :=

    http.StatusBadRequest log.Printf("%s: %s: %d", r.RemoteAddr, r.URL, code) errorCount.Add(1) http.Error(w, err.Error(), code) return } v, err := s.Sum(req.A, req.B) if err != nil { code := http.StatusInternalServerError log.Printf("%s: %s: %d", r.RemoteAddr, r.URL, code) errorCount.Add(1) http.Error(w, err.Error(), code) return } w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]int{"v": v}) log.Printf("%s: %s: %d", r.RemoteAddr, r.URL, 200)
 successCount.Add(1)
  31. 49.

    Transport Core Business Logic App Logging Metrics Tracing Circuit Breaking

    Rate Limiting Audit Logging Service Discovery Deploy Strategy Contract Testing Alerting Caching Strategy Security Service Registry :( :( :( ;( :( :(
  32. 51.
  33. 53.

    The central rule of The Clean Architecture is the Dependency

    Rule, which says source code dependencies can only point inwards. — http://appliedgo.net/di
  34. 55.

    type Service interface { Sum(ctx context.Context, a, b int) (int,

    error) Concat(ctx context.Context, a, b string) (string, error) }
  35. 56.

    type Service interface { Sum(ctx context.Context, a, b int) (int,

    error) Concat(ctx context.Context, a, b string) (string, error) } type basicService struct{}
  36. 57.

    type Service interface { Sum(ctx context.Context, a, b int) (int,

    error) Concat(ctx context.Context, a, b string) (string, error) } type basicService struct{} func (s basicService) Sum(_ context.Context, a, b int) (int, error) { if a == 0 && b == 0 { return 0, ErrTwoZeroes } if (b > 0 && a > (intMax-b)) || (b < 0 && a < (intMin-b)) { return 0, ErrIntOverflow } return a + b, nil }
  37. 58.

    type Service interface { Sum(ctx context.Context, a, b int) (int,

    error) Concat(ctx context.Context, a, b string) (string, error) } type basicService struct{} func (s basicService) Sum(_ context.Context, a, b int) (int, error) { if a == 0 && b == 0 { return 0, ErrTwoZeroes } if (b > 0 && a > (intMax-b)) || (b < 0 && a < (intMin-b)) { return 0, ErrIntOverflow } return a + b, nil } func (s basicService) Concat(_ context.Context, a, b string) (string, error) { if len(a)+len(b) > maxLen { return "", ErrMaxSizeExceeded } return a+b, nil }
  38. 61.

    type Middleware func(Service) Service type loggingMiddleware struct { logger log.Logger

    next Service } func NewLoggingMiddleware(logger log.Logger) Middleware { return func(next Service) Service { return loggingMiddleware{logger, next} } }
  39. 62.

    type Middleware func(Service) Service type loggingMiddleware struct { logger log.Logger

    next Service } func NewLoggingMiddleware(logger log.Logger) Middleware { return func(next Service) Service { return loggingMiddleware{logger, next} } } func (mw loggingMiddleware) Sum(ctx context.Context, a, b int) (v int, err error) { defer func() { mw.logger.Log("method", "Sum", "a", a, "b", b, "v", v, "err", err) }() return mw.next.Sum(ctx, a, b) } // Concat is the same
  40. 65.

    type Middleware func(Service) Service type instrumentingMiddleware struct { ints metrics.Counter

    chars metrics.Counter next Service } func NewInstrumentingMiddleware(ints, chars metrics.Counter) Middleware { return func(next Service) Service { return instrumentingMiddleware{ ints: ints, chars: chars, next: next, } } }
  41. 66.

    type Middleware func(Service) Service type instrumentingMiddleware struct { ints metrics.Counter

    chars metrics.Counter next Service } func NewInstrumentingMiddleware(ints, chars metrics.Counter) Middleware { return func(next Service) Service { return instrumentingMiddleware{ ints: ints, chars: chars, next: next, } } } func (mw instrumentingMiddleware) Sum(ctx context.Context, a, b int) (int, error) { v, err := mw.next.Sum(ctx, a, b) mw.ints.Add(v) return v, err }
  42. 72.

    type Endpoint func(ctx context.Context, req interface{}) (resp interface{}, err error)

    func MakeSumEndpoint(s Service) Endpoint { return func(ctx context.Context, req interface{}) (resp interface{}, err error) { request := req.(SumRequest) v, err := s.Sum(ctx, request.A, request.B) return SumResponse{V: v, Err: err}, nil } }
  43. 73.

    type Endpoint func(ctx context.Context, req interface{}) (resp interface{}, err error)

    func MakeSumEndpoint(s Service) Endpoint { return func(ctx context.Context, req interface{}) (resp interface{}, err error) { request := req.(SumRequest) v, err := s.Sum(ctx, request.A, request.B) return SumResponse{V: v, Err: err}, nil } } type SumRequest struct { A, B int } type SumResponse struct { V int Err error }
  44. 75.

    type Middleware func(Endpoint) Endpoint func NewCircuitBreakingMiddleware(cb *gobreaker.CircuitBreaker) Middleware { return

    func(next Endpoint) Endpoint { return func(ctx context.Context, req interface{}) (interface{}, error) { return cb.Execute(func() (interface{}, error) { return next(ctx, req) }) } } }
  45. 76.

    type Middleware func(Endpoint) Endpoint func NewCircuitBreakingMiddleware(cb *gobreaker.CircuitBreaker) Middleware { return

    func(next Endpoint) Endpoint { return func(ctx context.Context, req interface{}) (interface{}, error) { return cb.Execute(func() (interface{}, error) { return next(ctx, req) }) } } } func NewThrottlingMiddleware(b *ratelimit.Bucket) Middleware { return func(next Endpoint) Endpoint { return func(ctx context.Context, req interface{}) (interface{}, error) { time.Sleep(tb.Take(1)) return next(ctx, request) } } }
  46. 79.

    HTTP func NewServer( e endpoint.Endpoint, dec DecodeRequestFunc, enc EncodeResponseFunc, options

    ...ServerOption, ) *Server { // ...
 } func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { request, err := s.dec(ctx, r) if err != nil { return }
 response, err := s.e(ctx, request) if err != nil { return } if err := s.enc(ctx, w, response); err != nil { return } }
  47. 80.

    gRPC func NewServer( e endpoint.Endpoint, dec DecodeRequestFunc, enc EncodeResponseFunc, options

    ...ServerOption, ) *Server { // ...
 } func (s Server) ServeGRPC(ctx Context, req interface{}) (Context, interface{}, request, err := s.dec(ctx, req) if err != nil { s.logger.Log("err", err) return ctx, nil, err } response, err := s.e(ctx, request) if err != nil { return ctx, nil, err } grpcResp, err := s.enc(ctx, response) if err != nil { return ctx, nil, err } return ctx, grpcResp, nil }
  48. 81.
  49. 82.

    HTTP Mux gRPC Mux gRPC Server gRPC Server HTTP Server

    HTTP Server CB CB RL RL Concat Sum Logging Service
  50. 83.

    Simple & non-magical We embrace and extend Go’s philosophy, to

    be in service to software engineering in the large * *
  51. 86.

    Prometheus type metricsMiddleware struct { ints metrics.Counter chars metrics.Counter next

    Service } func (mw metricsMiddleware) Sum(ctx context.Context, a, b int) (int, error) { v, err := mw.next.Sum(ctx, a, b) mw.ints.Add(v) return v, err } func (mw metricsMiddleware) Concat(ctx context.Context, a, b string) (string, error) { v, err := mw.next.Concat(ctx, a, b) mw.chars.Add(len(v)) return v, err }
  52. 87.

    OpenTracing & Zipkin Transport Operational Metrics Balancing, Limiting, Safety Business

    Analytics Application Logging Service Metrics Business Logic Extract Inject Annotate & Collect
  53. 89.