Hardy
Hardy is a very simple wrapper around http.Client that enables you to add more resilience and reliability for your HTTP calls through retries. As retry strategy, Hardy will use the exponential algorithm and jitter to ensure that our services doesn't cause total outage to their dependencies.
You can read more:
Usage
Install
go get github.com/diegohordi/hardy
Creating the client
In order to create the Hardy client you will need to provide a http.Client and a log.Logger instances. If no log.Logger instance was given, the log will be disabled.
Additional parameters:
- WithMaxRetries - will determine how many retries should be attempted.
- WithWaitInterval - will define the base duration between each retry.
- WithMultiplier - the multiplier that should be used to calculate the backoff interval. Should be greater than the hardy.DefaultMultiplier.
- WithMaxInterval - the max interval between each retry. If no one was given, the interval between each retry will grow exponentially.
httpClient := &http.Client{Timeout: 3 * time.Second}
client := hardy.NewClient(httpClient, log.Default()).
WithMaxRetries(4).
WithWaitInterval(3 * time.Millisecond).
WithMultiplier(hardy.DefaultMultiplier).
WithMaxInterval(3 * time.Second)
Using the client
The wrapper adds the method Try(context.Context, *http.Request, hardy.ReaderFunc, hardy.FallbackFunc), which receives:
- context.Context a proper context to the request, mandatory. Hardy is also enabled to deal with context deadline/cancellation.
- *http.Request an instance of the request that should be performed, mandatory.
- hardy.ReaderFunc a reader function, mandatory, that will be responsible to handle each request result.
- hardy.FallbackFunc a fallback function that will be called if all retries fail, optional.
hardy.ReaderFunc
The ReaderFunc defines the function responsible to read the HTTP response and also determines if a new retry must be performed returning an error or not, returning nil.
Keep in mind while writing your reader function that we shouldn't perform a retry if the response contains an error due to a client error (400-499 HTTP error codes), but consider only the ones not caused by them instead, as 500 and 503 HTTP error codes, for instance.
Example
type TestService struct {
Client *hardy.Client
}
func (s *TestService) GetHelloMessage(ctx context.Context, name string) (string, error) {
if s.Client == nil {
return "", fmt.Errorf("no client was given")
}
var helloMessage string
request, err := http.NewRequest(http.MethodGet, "https://httpbin.org/status/500,200,300", nil)
if err != nil {
return "", err
}
readerFunc := func(message *string) hardy.ReaderFunc {
return func(response *http.Response) error {
if response.StatusCode == http.StatusOK {
*message = fmt.Sprintf("hello from reader, %s!", name)
return nil
}
return fmt.Errorf(response.Status)
}
}
fallbackFunc := func(message *string) hardy.FallbackFunc {
return func() error {
*message = fmt.Sprintf("hello from fallback, %s!", name)
return nil
}
}
err = s.Client.Try(ctx, request, readerFunc(&helloMessage), fallbackFunc(&helloMessage))
if err != nil {
return "", err
}
return helloMessage, nil
}
...
httpClient := &http.Client{Timeout: 3 * time.Second}
client := hardy.NewClient(httpClient, log.Default()).
WithMaxRetries(4).
WithWaitInterval(3 * time.Millisecond).
WithMultiplier(hardy.DefaultMultiplier).
WithMaxInterval(3 * time.Second)
testService := &TestService{Client: client}
msg, err := testService.GetHelloMessage(ctx, tt.args.name)
Tests
The coverage so far is greater than 90%, covering also failure scenarios. Also, there are no race conditions detected in the -race tests.
You can run the tests and the benchmark from Makefile, as below:
Test
make test
Benchmarks
make benchmark
TODO
- Improve logs