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 Slide

  2. Go is a good language

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

    View Slide

  3. What is Go?

    View Slide

  4. 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 Slide

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

    View Slide

  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

    View Slide

  7. “Simple”

    View Slide

  8. “No magic”

    View Slide

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

    View Slide

  10. View Slide

  11. Mindshare

    View Slide

  12. Mindshare

    View Slide

  13. Microservices

    View Slide

  14. View Slide

  15. Microservices solve
    organizational problems

    ~

    Microservices cause

    technical problems

    View Slide

  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

    View Slide

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

    View Slide

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

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

    • Distributed tracing?

    • Build pipelines??

    • Security???

    View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. View Slide

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

    View Slide

  24. Enter Go kit

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  30. Example service

    View Slide

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

    View Slide

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

    View Slide

  33. Naïve first draft

    View Slide

  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

    View Slide

  35. Transport
    Core
    Business
    Logic

    View Slide

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

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

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

    View Slide

  40. case "/concat":

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

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

    View Slide

  44. Transport
    Core
    Business
    Logic
    App
    Logging

    View Slide

  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)

    View Slide

  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
    }

    View Slide

  47. Transport
    Core
    Business
    Logic
    App
    Logging
    Metrics

    View Slide

  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)

    View Slide

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

    View Slide

  50. The onion model

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  54. Service layer

    View Slide

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

    View Slide

  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{}

    View Slide

  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
    }

    View Slide

  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
    }

    View Slide

  59. type Middleware func(Service) Service

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  63. type Middleware func(Service) Service

    View Slide

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

    View Slide

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

    View Slide

  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
    }

    View Slide

  67. And a lot more...!

    View Slide

  68. Endpoint layer

    View Slide

  69. type Endpoint func(req) resp

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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
    }

    View Slide

  74. type Middleware func(Endpoint) Endpoint

    View Slide

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

    View Slide

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

    View Slide

  77. Transport layer

    View Slide

  78. Endpoint
    HTTP, gRPC, etc.
    Decode

    Request Encode

    Response

    View Slide

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

    View Slide

  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
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  84. Maintainability
    Optimize for
    above all else!

    View Slide

  85. Integration with the

    Cloud Native stack

    View Slide

  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
    }

    View Slide

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

    View Slide

  88. OpenTracing & Zipkin
    Extract
    Inject
    Annotate & Collect

    View Slide

  89. Demo

    View Slide

  90. Where to Go from here?

    View Slide

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

    View Slide

  92. gophercon.com

    View Slide

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

    View Slide