CodeFest 2019. Максим Чечель (Juno) — Инструментирование Go кода

16b6c87229eaf58768d25ed7b2bbbf52?s=47 CodeFest
April 05, 2019

CodeFest 2019. Максим Чечель (Juno) — Инструментирование Go кода

В то время как язык Go предоставляет нам много таких крутых инструментов как go-рутины, каналы, утиная типизация, быстрый сборщик мусора, маленький рантайм и компиляцию в нативный код под множество платформ, мы, как разработчики, часто страдаем от того, что нам нужно платить дань в виде рутинного, повторяющегося кода. В своём докладе я продемонстрирую, что несмотря на расхожее мнение, отсутствие дженериков это далеко не основная проблема Go, с которой мы сталкиваемся каждый день на практике. Мы поговорим о применимости и применении такого шаблона проектирования как «декоратор» для инструментирования Go кода. На примерах из реальной жизни, я покажу, каким образом можно решить типовые инженерные проблемы с помощью одного простого инструмента. Вы узнаете, как можно победить рутину и добавить такую функциональность как метрики, трейсинг, повторы, предохранители, логгирование и другую функциональность в ваши проекты на Go за считанные секунды!

16b6c87229eaf58768d25ed7b2bbbf52?s=128

CodeFest

April 05, 2019
Tweet

Transcript

  1. Инструментирование Go кода Максим Чечель Бэкенд разработчик Juno, Минск

  2. Способы инструментирования кода <2 - По месту (inline) - Декораторы

    (middleware)
  3. Инструментирование кода по месту <3 func (c Calculator) Sum(a, b

    int) int { sum := a + b return sum }
  4. Инструментирование кода по месту <4 func (c Calculator) Sum(a, b

    int) int { sum := a + b log.Printf("%d + %d = %d\n", a, b, sum) return sum }
  5. Инструментирование кода по месту <5 func (c Calculator) Sum(a, b

    int) int { sum := a + b if c.Config.Logger.Enable { log.Printf("%d + %d = %d\n", a, b, sum) } return sum }
  6. Инструментирование кода по месту <6 func (c Calculator) Sum(a, b

    int) int { before := time.Now() sum := a + b if c.Config.Logger.Enable { runTime := time.Now().Sub(before) log.Printf("%d + %d = %d, done in %v\n", a, b, sum, runTime) } return sum }
  7. Инструментирование кода по месту <7 func (c Calculator) Sum(a, b

    int) int { before := time.Now() sum := a + b if c.Config.Logger.Enable { runTime := time.Now().Sub(before) log.Printf("%d + %d = %d, done in %v\n", a, b, sum, runTime) } return sum }
  8. Инструментирование кода с использованием декораторов - func (InterfaceType) InterfaceType -

    func (FuncType) FuncType <8
  9. Инструментирование кода с использованием декораторов - func (InterfaceType) InterfaceType Декораторы

    в стандартной библиотеке Go ??? <9
  10. Инструментирование кода с использованием декораторов - bufio.NewReader, bufio.NewWriter - gzip.NewReader,

    gzip.NewWriter - io.TeeReader - io.LimitedReader - ioutil.NopCloser <10
  11. Инструментирование кода с использованием декораторов type summator interface { Sum(int,

    int) int } type SummatorWithLogging struct { s summator } func (s SummatorWithLogging) Sum(a, b int) (sum int) { defer log.Printf("%d + %d = %d\n", a, b, sum) return s.s.Sum(a, b) } <11
  12. Декораторы для “net/http” - http.Handler - http.RoundTripper - Do(*http.Request) (*http.Response,

    error) <12
  13. Декораторы для “net/http” import "github.com/gorilla/handlers"
 
 var h http.Handler
 h

    = handlers.CompressHandler(h)
 h = handlers.LoggingHandler(os.Stdout, h) <13
  14. Декораторы для “net/http” import "github.com/improbable-eng/go-httpwares/retry"
 
 var t = http.DefaultTransport


    client := http.Client{
 Transport: retry.Tripperware().(t),
 }
 client.Get(“https://google.com”) <14
  15. Декораторы для “net/http” <15 Сторонний сервис foo.NewClient(c HTTPClient) c :=

    WithTracing(http.DefaultClient)
 c = WithMetrics(c)
 client := foo.NewClient(c) Наш сервис type HTTPClient interface {
 Do(*http.Request) (*http.Response, error)
 } API метод HTTP метод
  16. Декораторы для “net/http” <16 Сторонний сервис foo.NewClient(c HTTPClient) Наш сервис

    API метод HTTP метод Теряем контекст
  17. Самая большая проблема <17 type DuckPigger interface {
 Quack() //кря


    Oink() //хрю
 } Playhouse Disney - Mr. Pig and Mr. Duck
  18. Декораторы на каждый день <18 - Трейсинг (tracing) - Метрики

    (metrics) - Повторители (retry) - Выключатели (circuit breaker) - Резервирование (fallback) - Ограничители потока запросов (rate limit)
  19. Повторитель / Retry <19 // Close implements io.Closer
 func (d

    CloserWithRetry) Close() (err error) {
 for i := 0; i < d.retryCount; i++ {
 err = d.Closer.Close(p)
 if err == nil {
 break
 }
 if d.retryCount > 1 {
 time.Sleep(d.retryInterval)
 }
 }
 return err
 }
  20. Резервирование / Fallback <20 Google Distance Matrix API OSRM API

    Таймаут Отмена запроса к OSRM Результат
  21. Резервирование / Fallback <21 Google Distance Matrix API OSRM API

    Таймаут Отмена запроса к Google Результат
  22. Резервирование / Fallback <22 Google Distance Matrix API OSRM API

    Таймаут Агрегированная ошибка
  23. <23 func (d ClientWithFallback) BestETA(ctx context.Context, p Point, ds []Point)

    (i1 int, err error) {
 type resultStruct struct { i1 int; err error}
 var res resultStruct
 var ch = make(chan resultStruct, 0)
 var errorsList []string
 var ticker = time.NewTicker(d.interval)
 defer ticker.Stop()
 ctx, cancelFunc := context.WithCancel(ctx)
 defer cancelFunc()
 for i := 0; i < len(d.implementations); i++ {
 go func(impl Client) {
 i1, err := impl.BestETA(ctx, p, ds) // ← бизнес логика
 if err != nil {
 err = fmt.Errorf("%T: %v", impl, err)
 }
 ch <- resultStruct{i1, err}
 }(d.implementations[i])
 select {
 case res = <-ch:
 if res.err == nil {
 return res.i1, res.err
 }
 errorsList = append(errorsList, res.err.Error())
 case <-ticker.C:
 }
 }
 return 0, fmt.Errorf(strings.Join(errorsList, ";")) }
  24. <24 5 интерфейсов 4 метода в каждом 3 декоратора 60

    методов
  25. Как избавиться от этой рутины? <25 Интерфейс Декоратор Генератор

  26. GoWrap <26 Method{*ast.FuncType} ReturnsError AcceptsContext ResultsNames ResultStruct Call Pass github.com/hexdigest/gowrap

  27. Пример шаблона <27 {{if $method.ReturnsError}} // {{$method.Name}} implements {{$.Interface.Type}} func

    (_d {{$decorator}}) {{$method.Declaration}} { for _i := 0; _i < _d._retryCount; _i++ { {{$method.ResultsNames}} = _d.{{$.Interface.Name}}.{{$method.Call}} if err == nil { break } if _d._retryCount > 1 { time.Sleep(_d._retryInterval) } } return {{$method.ResultsNames}} } {{end}}
  28. Повторяемая кодогенерация <28 gowrap gen -p io -i Reader -t

    prometheus -o out.go package mypackage
 
 // DO NOT EDIT!
 // This code is generated with http://github.com/hexdigest/gowrap tool
 // using prometheus template
 
 //go:generate gowrap gen -p io -i Reader -o out.go -t https:// raw.githubusercontent.com/hexdigest/gowrap/ 82337ad9093a2d6e7420e74a7e0e5058739ea014/templates/prometheus 

  29. Повторяемая кодогенерация <29 gowrap template copy prometheus tmpl/prometheus gowrap gen

    -p io -i Reader -t tmpl/prometheus -o out.go package mypackage
 
 // DO NOT EDIT!
 // This code is generated with http://github.com/hexdigest/gowrap tool
 // using tmpl/prometheus template
 
 //go:generate gowrap gen -p io -i Reader -o out.go -t tmpl/prometheus 

  30. <30 $ go generate ./...

  31. Доступные шаблоны <31 $ gowrap template list List of available

    remote templates:
 circuitbreaker
 fallback
 logrus
 opentracing
 prometheus
 ratelimit
 retry
 robinpool
 syncpool
  32. Пулы <32 Вызов метода robinpool syncpool

  33. Предохранитель (circuitbreaker) <33 Замкнут Разомкнут Проверка Превышено кол-во ошибок Таймаут

    проверки состояния Ошибка Успех Успех Ошибка (до порога)
  34. Метрики (prometheus) <34

  35. Метрики (prometheus) <35 // Ping implements driver.Pinger
 func (d PingerWithPrometheus)

    Ping(ctx context.Context) (err error) {
 since := time.Now()
 defer func() {
 result := "ok"
 if err != nil {
 result = "error"
 }
 pingerDurationSummaryVec.WithLabelValues(d.instanceName, "Ping", result).Observe(time.Since(since).Seconds())
 }()
 return d.base.Ping(ctx)
 }
  36. Трассировка (opentracing) <36

  37. <37 func (d PingerWithTracing) Ping(ctx context.Context) (err error) {
 span,

    ctx := opentracing.StartSpanFromContext(ctx, d.instance +
 ".driver.Pinger.Ping") defer func() { if err != nil {
 ext.Error.Set(span, true)
 span.LogFields(
 log.String("event", "error"), log.String("message", err.Error()),
 )
 }
 span.Finish()
 }()
 return d.Pinger.Ping(ctx) } Трассировка (opentracing)
  38. <38 DRY

  39. @maxchechel Максим Чечель Бэкенд разработчик Juno, Минск Вопросы? github.com/hexdigest. hexdigest@gmail.com