Slide 1

Slide 1 text

DESIGN YOUR CLIENT Mike Kabischev

Slide 2

Slide 2 text

JUNO ▸250k lines of code ▸~70 microservices ▸hundreds of running containers ▸thousands RPS ▸40+ backend engineers

Slide 3

Slide 3 text

CODE BELONGS TO THE TEAM, NOT THE INDIVIDUAL

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

CLEAN CODE ▸Simple ▸Adaptable ▸Testable

Slide 6

Slide 6 text

ONE CLIENT STORY

Slide 7

Slide 7 text

3-RD PARTY SERVICE A SERVICE B SERVICE C

Slide 8

Slide 8 text

3-RD PARTY SERVICE A SERVICE B SERVICE C

Slide 9

Slide 9 text

HTTP CLIENT

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

HTTP CLIENT ‣Authorization ‣Logging ‣Instrumentation ‣Backoff retries ‣Load balancing

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

EVERYTHING IS ALREADY INVENTED

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

MIDDLEWARE // Middleware adds additional behaviour to handler type Middleware func(http.Handler) http.Handler

Slide 18

Slide 18 text

MIDDLEWARE CHAIN HANDLER REQUEST Middleware A Middleware B Middleware C

Slide 19

Slide 19 text

MIDDLEWARE CHAIN HANDLER REQUEST Middleware A Middleware B Middleware C

Slide 20

Slide 20 text

MIDDLEWARE CHAIN HANDLER REQUEST Middleware A Middleware B Middleware C

Slide 21

Slide 21 text

MIDDLEWARE CHAIN HANDLER REQUEST Middleware A Middleware B Middleware C

Slide 22

Slide 22 text

MIDDLEWARE CHAIN HANDLER REQUEST Middleware A Middleware B Middleware C

Slide 23

Slide 23 text

MIDDLEWARE CHAIN HANDLER RESPONSE Middleware A Middleware B Middleware C

Slide 24

Slide 24 text

MIDDLEWARE CHAIN CLIENT RESPONSE Middleware A Middleware B Middleware C

Slide 25

Slide 25 text

MIDDLEWARE CHAIN CLIENT RESPONSE Middleware A Middleware B Middleware C

Slide 26

Slide 26 text

MIDDLEWARE CHAIN HANDLER RESPONSE Middleware A Middleware B Middleware C

Slide 27

Slide 27 text

MIDDLEWARE CHAIN HANDLER RESPONSE Middleware A Middleware B Middleware C

Slide 28

Slide 28 text

PROJECT IT TO THE CLIENT SIDE

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

MIDDLEWARE // Middleware adds additional behaviour to Client type Middleware func(Client) Client

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

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 }

Slide 39

Slide 39 text

MIDDLEWARE CHAIN client := Chain( &http.Client{}, Logging(log.New(os.Stdout, "http: ", log.LstdFlags)), )

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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"), )

Slide 44

Slide 44 text

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(), )

Slide 45

Slide 45 text

http://idiomaticgo.com/post/best-practice/accept-interfaces-return-structs/ ACCEPT INTERFACES RETURN STRUCTS

Slide 46

Slide 46 text

https://github.com/gojuno/go-zooz GO WAY

Slide 47

Slide 47 text

NOT GO WAY googlemaps.github.io/maps github.com/aws/aws-sdk-go github.com/coreos/go-etcd github.com/moby/moby github.com/openzipkin/zipkin-go-opentracing github.com/stripe/stripe-go ... and more ...

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

DON'T FORGET THE BASICS https://go-proverbs.github.io https://golang.org/doc/effective_go.html https://github.com/golang/go/wiki/CodeReviewComments

Slide 50

Slide 50 text

SIMPLICITY IS COMPLICATED Rob Pike

Slide 51

Slide 51 text

QUESTIONS?