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.
16. // 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
29. 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)
}
33. 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)
})
}
}
34. 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
})
}
}
35. 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®
})
}
}
36. 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)
})
}
}
37.
38. 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
}