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

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

CodeFest
April 05, 2019

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

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

CodeFest

April 05, 2019
Tweet

More Decks by CodeFest

Other Decks in Technology

Transcript

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

    int) int { sum := a + b log.Printf("%d + %d = %d\n", a, b, sum) return sum }
  2. Инструментирование кода по месту <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 }
  3. Инструментирование кода по месту <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 }
  4. Инструментирование кода по месту <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 }
  5. Инструментирование кода с использованием декораторов 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
  6. Декораторы для “net/http” import "github.com/gorilla/handlers"
 
 var h http.Handler
 h

    = handlers.CompressHandler(h)
 h = handlers.LoggingHandler(os.Stdout, h) <13
  7. Декораторы для “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
  8. Декораторы для “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 метод
  9. Декораторы на каждый день <18 - Трейсинг (tracing) - Метрики

    (metrics) - Повторители (retry) - Выключатели (circuit breaker) - Резервирование (fallback) - Ограничители потока запросов (rate limit)
  10. Повторитель / 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
 }
  11. Резервирование / Fallback <20 Google Distance Matrix API OSRM API

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

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

    Таймаут Агрегированная ошибка
  14. <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, ";")) }
  15. Пример шаблона <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}}
  16. Повторяемая кодогенерация <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 

  17. Повторяемая кодогенерация <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 

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

    remote templates:
 circuitbreaker
 fallback
 logrus
 opentracing
 prometheus
 ratelimit
 retry
 robinpool
 syncpool
  19. Предохранитель (circuitbreaker) <33 Замкнут Разомкнут Проверка Превышено кол-во ошибок Таймаут

    проверки состояния Ошибка Успех Успех Ошибка (до порога)
  20. Метрики (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)
 }
  21. <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)