retry 4.0

retry 4.0

Что нового будет в этом релизе

Пакет retry очень удобно использовать для написание сетевых клиентов, например, поверх стандартного net/http.Client.

type client struct {
	base       *http.Client
	deadline   <-chan struct{}
	strategies []strategy.Strategy
}

func New(timeout time.Duration, strategies ...strategy.Strategy) *client {
	return &client{
		base:       &http.Client{Timeout: timeout},
		strategies: strategies,
	}
}

func (c *client) WithDeadline(deadline <-chan struct{}) *client {
	return &client{
		base:       c.base,
		deadline:   deadline,
		strategies: c.strategies,
	}
}

func (c *client) Get(url string) (*http.Response, error) {
	var response *http.Response
	err := retry.Retry(c.deadline, func(uint) error {
		resp, err := c.base.Get(url)
		if err != nil {
			return err
		}
		response = resp
		return nil
	}, c.strategies...)
	return response, err
}

resp, err := New(time.Second).WithDeadline(req.Context().Done()).Get("https://kamil.samigullin.info/")

В более сложных случаях, например, когда требуется выполнить множество зависимых действий в цепочке, возможностей пакета не хватает.

Суть проблемы

В веб-приложениях с сервис-ориентированной архитектурой один пользовательский запрос может порождать множество подзапросов (как параллельных, так и последовательных) к различным подсистемам, стопроцентная доступность которых не гарантируется. Это вынуждает разработчика использовать такие пакеты как retry.

Простота данного пакета это одновременно и его сила, и слабость - приходится писать много повторяемого кода из проекта в проект.

Задача

Необходимо выделить типовые паттерны использования пакета retry и поддержать их из коробки. Также необходимо довести работу с пакетом classifier до ума.

type Temporary interface {
	Temporary() bool
}

type HttpError struct {
	Code int
}

func (err HttpError) Error() string {
	return fmt.Sprintf("http: status code %d", err.Code)
}

func (err HttpError) Temporary() bool {
	switch err.Code {
	case http.StatusRequestTimeout:
	case http.StatusBadGateway:
	case http.StatusServiceUnavailable:
		return true
	}
	return false
}

func CheckNetworkError() Strategy {
	return func(attempt uint, err error) bool {
		if err, ok := err.(net.Error); ok {
			return err.Timeout() || err.Temporary()
		}
		return true
	}
}

func CheckStatusCode() Strategy {
	func(attempt uint, err error) bool {
		if err, ok := err.(Temporary); ok {
			return err.Temporary()
		}
		return true
	}
}

Возможное решение

Декларативное построение цепочки действий.

err := retry.Builder().
	Before(transaction.Start).
	Try(action.DoSomething).
	Try(action.DoSomethingElse).
	Parallel().
	Try(transaction.Commit).
	Otherwise(transaction.Rollback).
	Finally(connection.Close).
	Do(req.Context().Done())

Разработка

Планируемая версия будет содержать не только решение озвученной выше задачи, но и ряд дополнительных улучшений. Подробнее:

Теги

go retry

Автор

Камиль Самигуллин
Камиль Самигуллин

Разработчик

Есть потребность в более высокоуровневом компоненте, который позволял бы работать с пакетом retry декларативно. Также пакет classifier интегрирован не полноценно, что я и планирую исправить в ближайшее время.

Спонсоры

Опубликовано

23.12.2017