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

Design your client: go way

Design your client: go way

The Golang standard library provides a lot of examples of code design practices and patterns which allow us to create great things. However in practice engineers often ignore them.
I`m going to tell the story about one little library: the way from write-to-work stage to the idiomatic go implementation.

Mike Kabischev

July 21, 2017
Tweet

Other Decks in Technology

Transcript

  1. JUNO ▸250k lines of code ▸~70 microservices ▸hundreds of running

    containers ▸thousands RPS ▸40+ backend engineers
  2. // HTTPClient wraps httpClient type HTTPClient struct { httpClient *http.Client

    } // Do sends HTTP request func (c *HTTPClient) Do(r *http.Request) (*http.Response, error) { return http.DefaultClient.Do(r) } HTTP CLIENT
  3. // Do sends HTTP request with authorization, load balancing, //

    retries, metrics & logging func (c *HTTPClient) Do(r *http.Request) (res *http.Response, err error) { r.Header.Add("Authorization", c.AuthToken) for i := 0; i < c.RetryAttempts; i++ { r.URL.Host = c.Backends[rand.Intn(len(c.Backends))] c.Logger.Printf("make HTTP call. Method: %v. URL: %v", r.Method, r.URL) start := time.Now() res, err = http.DefaultClient.Do(r) c.DurationSummary.Observe(time.Since(start).Seconds()) if err != nil { c.Logger.Printf("error while making HTTP call: %v", err) time.Sleep(time.Duration(i) * c.RetryDelay) continue } break } c.Logger.Printf("response code: %v", res.StatusCode) return res, err } HTTP CLIENT
  4. // Do sends HTTP request with authorization, load balancing, //

    retries, metrics & logging func (c *HTTPClient) Do(r *http.Request) (res *http.Response, err error) { r.Header.Add("Authorization", c.AuthToken) for i := 0; i < c.RetryAttempts; i++ { r.URL.Host = c.Backends[rand.Intn(len(c.Backends))] c.Logger.Printf("make HTTP call. Method: %v. URL: %v", r.Method, r.URL) start := time.Now() res, err = http.DefaultClient.Do(r) c.DurationSummary.Observe(time.Since(start).Seconds()) if err != nil { c.Logger.Printf("error while making HTTP call: %v", err) time.Sleep(time.Duration(i) * c.RetryDelay) continue } break } c.Logger.Printf("response code: %v", res.StatusCode) return res, err } HTTP CLIENT
  5. // A Handler responds to an HTTP request. type Handler

    interface { ServeHTTP(ResponseWriter, *Request) } // The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // Handler that calls f. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } HTTP HANDLERS
  6. CLIENT INTERFACE // Client is an interface supported by http.Client

    type Client interface { Do(req *http.Request) (*http.Response, error) } // ClientFn is a function that implements Client type ClientFn func(*http.Request) (*http.Response, error) // Do is an implementation of Client interface func (c ClientFn) Do(r *http.Request) (*http.Response, error) { return c(r) }
  7. LOGGING // Logging returns a Middleware that logs request func

    Logging(l *log.Logger) Middleware { return func(c Client) Client { return ClientFn(func(r *http.Request) (*http.Response, error) { l.Printf("HTTP call. Method: %v. URL: %v", r.Method, r.URL) return c.Do(r) }) } }
  8. HEADERS // Header adds specified header to request func Header(key,

    value string) Middleware { return func(c Client) Client { return ClientFn(func(r *http.Request) (*http.Response, error) { r.Header.Add(key, value) return c.Do(r) }) } } // BasicAuthorization adds header for Basic HTTP Authorization func BasicAuthorization(username, password string) Middleware { value := []byte(username + ":" + password) encoded := base64.StdEncoding.EncodeToString(value) return Header("Authorization", "Basic "+encoded) }
  9. METRICS // Metrics returns a Middleware that measures the time

    taken by request func Metrics(s prometheus.Summary) Middleware { return func(c Client) Client { return ClientFn(func(r *http.Request) (*http.Response, error) { defer func(t time.Time) { s.Observe(time.Since(t).Seconds()) }(time.Now()) return c.Do(r) }) } }
  10. BACKOFF // Backoff returns a Middleware that makes configured number

    of attemps // to make HTTP call func Backoff(attemps int, delay time.Duration) Middleware { return func(c Client) Client { return ClientFn(func(r *http.Request) (res *http.Response, err error) { for i := 0; i < attemps; i++ { if res, err := c.Do(r); err == nil { break } time.Sleep(time.Duration(i) * delay) } return res, err }) } }
  11. LOAD BALANCING // LoadBalancing returns a Middleware that chooses random

    backend from // the specified list func LoadBalancing(backends ...string) Middleware { return func(c Client) Client { return ClientFn(func(r *http.Request) (*http.Response, error) { r.URL.Host = backends[rand.Intn(len(backends))] return c.Do® }) } }
  12. RECOVER // Recover returns a middleware that recovers all panics

    func Recover() Middleware { return func(c Client) Client { return ClientFn(func(r *http.Request) (res *http.Response, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("recovered panic: %v", r) } }() return c.Do(r) }) } }
  13. MIDDLEWARE CHAIN // Chain wraps a Client c with all

    the given middlewares func Chain(c Client, mw ...Middleware) Client { result := c for _, middleware := range mw { result = middleware(result) } return result }
  14. MIDDLEWARE CHAIN client := Chain( &http.Client{}, Logging(log.New(os.Stdout, "http: ", log.LstdFlags)),

    BasicAuthorization("username", "password"), Metrics(NewSummary("request_duration_seconds")), )
  15. MIDDLEWARE CHAIN client := Chain( &http.Client{}, Logging(log.New(os.Stdout, "http: ", log.LstdFlags)),

    BasicAuthorization("username", "password"), Metrics(NewSummary("request_duration_seconds")), Backoff(3, 20*time.Millisecond), )
  16. MIDDLEWARE CHAIN client := Chain( &http.Client{}, Logging(log.New(os.Stdout, "http: ", log.LstdFlags)),

    BasicAuthorization("username", "password"), Metrics(NewSummary("request_duration_seconds")), Backoff(3, 20*time.Millisecond), LoadBalancing("instance01", "instance02", "instance03"), )
  17. MIDDLEWARE CHAIN client := Chain( &http.Client{}, Logging(log.New(os.Stdout, "http: ", log.LstdFlags)),

    BasicAuthorization("username", "password"), Metrics(NewSummary("request_duration_seconds")), Backoff(3, 20*time.Millisecond), LoadBalancing("instance01", "instance02", "instance03"), Recover(), )