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

Go + microservices = Go kit

Go + microservices = Go kit

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

Peter Bourgon

March 29, 2017
Tweet

More Decks by Peter Bourgon

Other Decks in Programming

Transcript

  1. Go + microservices = Go kit
    @peterbourgon · Cloud Native Con 2017 · Berlin

    View full-size slide

  2. Go is a good language

    for microservices
    I want to convince you that
    and maybe Go kit will help

    View full-size slide

  3. History
    Date: Sun, 23 Sep 2007 23:33:41 -0700

    From: "Robert Griesemer" 

    To: "Rob 'Commander' Pike" , [email protected]
    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

    ...

    View full-size slide

  4. Go properties
    • Statically typed
    • Compiled — fast!
    • Native binaries
    • Garbage collected
    • Looks like C
    • Big standard library
    • Baked-in concurrency

    View full-size slide

  5. 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

    View full-size slide

  6. “Simple”

    View full-size slide

  7. “No magic”

    View full-size slide

  8. https://talks.golang.org/2012/splash.article

    View full-size slide

  9. Microservices

    View full-size slide

  10. Microservices solve
    organizational problems

    ~

    Microservices cause

    technical problems

    View full-size slide

  11. 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

    View full-size slide

  12. 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...

    View full-size slide

  13. Problems caused
    • Addressability i.e. service discovery

    • Monitoring and instrumentation — tail -f? Nagios & New Relic? Ha!

    • Distributed tracing?

    • Build pipelines??

    • Security???

    View full-size slide

  14. Concerns for a single service, Sean Treadway, SoundCloud

    View full-size slide

  15. Enter Go kit

    View full-size slide

  16. 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

    View full-size slide

  17. Current goals
    • Mostly the same
    • Even less opinionated about infrastructure than anticipated
    • More opinionated about application architecture than anticipated

    View full-size slide

  18. 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

    View full-size slide

  19. 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

    View full-size slide

  20. I don’t care if you use Go kit;
    I do care if you use Go
    I really want to emphasize

    View full-size slide

  21. Example service

    View full-size slide

  22. type AddService interface {
    Sum(a, b int) int
    Concat(a, b string) string
    }

    View full-size slide

  23. type AddService interface {
    Sum(a, b int) (int, error)
    Concat(a, b string) (string, error)
    }

    View full-size slide

  24. Naïve first draft

    View full-size slide

  25. 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

    View full-size slide

  26. Transport
    Core
    Business
    Logic

    View full-size slide

  27. func (s basicService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch r.URL.Path {
    case "/sum":

    View full-size slide

  28. 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
    }

    View full-size slide

  29. 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
    }

    View full-size slide

  30. 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})

    View full-size slide

  31. case "/concat":

    View full-size slide

  32. 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
    }

    View full-size slide

  33. 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
    }

    View full-size slide

  34. 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})

    View full-size slide

  35. Transport
    Core
    Business
    Logic
    App
    Logging

    View full-size slide

  36. 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)

    View full-size slide

  37. 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
    }

    View full-size slide

  38. Transport
    Core
    Business
    Logic
    App
    Logging
    Metrics

    View full-size slide

  39. 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)

    View full-size slide

  40. 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
    :(
    :(
    :( ;(
    :(
    :(

    View full-size slide

  41. The onion model

    View full-size slide

  42. Transport
    Operational Metrics
    Balancing, Limiting, Safety
    Business Analytics
    Application Logging
    Service Metrics
    Business Logic
    Service
    Endpoint
    Transport

    View full-size slide

  43. http://fideloper.com/hexagonal-architecture

    View full-size slide

  44. The central rule of The Clean Architecture
    is the Dependency Rule, which says
    source code dependencies
    can only point inwards.

    http://appliedgo.net/di

    View full-size slide

  45. Service layer

    View full-size slide

  46. type Service interface {
    Sum(ctx context.Context, a, b int) (int, error)
    Concat(ctx context.Context, a, b string) (string, error)
    }

    View full-size slide

  47. type Service interface {
    Sum(ctx context.Context, a, b int) (int, error)
    Concat(ctx context.Context, a, b string) (string, error)
    }
    type basicService struct{}

    View full-size slide

  48. 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
    }

    View full-size slide

  49. 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
    }

    View full-size slide

  50. type Middleware func(Service) Service

    View full-size slide

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

    View full-size slide

  52. 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}
    }
    }

    View full-size slide

  53. 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

    View full-size slide

  54. type Middleware func(Service) Service

    View full-size slide

  55. type Middleware func(Service) Service
    type instrumentingMiddleware struct {
    ints metrics.Counter
    chars metrics.Counter
    next Service
    }

    View full-size slide

  56. 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,
    }
    }
    }

    View full-size slide

  57. 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
    }

    View full-size slide

  58. And a lot more...!

    View full-size slide

  59. Endpoint layer

    View full-size slide

  60. type Endpoint func(req) resp

    View full-size slide

  61. type Endpoint func(req interface{}) (resp interface{})

    View full-size slide

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

    View full-size slide

  63. 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
    }
    }

    View full-size slide

  64. 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
    }

    View full-size slide

  65. type Middleware func(Endpoint) Endpoint

    View full-size slide

  66. 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) })
    }
    }
    }

    View full-size slide

  67. 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)
    }
    }
    }

    View full-size slide

  68. Transport layer

    View full-size slide

  69. Endpoint
    HTTP, gRPC, etc.
    Decode

    Request Encode

    Response

    View full-size slide

  70. 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
    }
    }

    View full-size slide

  71. 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
    }

    View full-size slide

  72. HTTP Mux
    HTTP Server
    HTTP Server
    CB
    CB
    RL RL
    Concat
    Sum
    Logging
    Service

    View full-size slide

  73. HTTP Mux
    gRPC Mux
    gRPC Server
    gRPC Server
    HTTP Server
    HTTP Server
    CB
    CB
    RL RL
    Concat
    Sum
    Logging
    Service

    View full-size slide

  74. Simple & non-magical
    We embrace and extend Go’s philosophy, to be
    in service to software engineering in the large
    * *

    View full-size slide

  75. Maintainability
    Optimize for
    above all else!

    View full-size slide

  76. Integration with the

    Cloud Native stack

    View full-size slide

  77. 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
    }

    View full-size slide

  78. OpenTracing & Zipkin
    Transport
    Operational Metrics
    Balancing, Limiting, Safety
    Business Analytics
    Application Logging
    Service Metrics
    Business Logic
    Extract
    Inject
    Annotate &
    Collect

    View full-size slide

  79. OpenTracing & Zipkin
    Extract
    Inject
    Annotate & Collect

    View full-size slide

  80. Where to Go from here?

    View full-size slide

  81. invite.slack.golangbridge.org appliedgo.net
    gobyexample.com
    gokit.io

    View full-size slide

  82. gophercon.com

    View full-size slide

  83. Thanks
    @peterbourgon · Cloud Native Con 2017 · Berlin

    View full-size slide