Part of the value of having an ambiguous retry package will be to be able to define ad-hoc logic.. and call it using the same method each time.
So let's look at the two things we will need in order to perform a retry.
- The retryer library code
- The retryable implementation
Imagine a Retryable
inteface such as the following. The interface defines Try()
method that can optionally return an error
.
type Retryable interface {
Try() error
}
Now immagine a go package that has library code to run a Retryer
such as
type Retryer struct {
retries int
sleepSeconds int
retryable Retryable
}
with a function that could be used to create a new Retrier
func NewRetrier(retries, sleepSeconds int, retryable Retryable) *Retrier {
return &Retrier{
retries: retries,
sleepSeconds: sleepSeconds,
retryable: retryable,
}
}
and some simple logic to try to run a function
func (r *Retryer) RunRetry() error {
for i := 0; i < r.retries; i++ {
err := r.retryable.Try()
if err != nil {
logger.Info("Retryable error: %v", err)
time.Sleep(time.Duration(r.sleepSeconds) * time.Second)
continue
}
return nil
}
return fmt.Errorf("Unable to succeed at retry after %d attempts at %d seconds", r.retries, r.sleepSeconds)
}
This gives the user calling the retry code a flexible approach to running the retry.
Imagine a method on a struct that tried to send an HTTP request
type Request struct {
// Information about the HTTP request
}
func (r *Request) Try(){
// Use members from *Request to send HTTP request
// Error if the request failed
}
We could then cleverly craft a Retry
to use it in the following way:
func main() {
request := &Request{
// Set data for the request
}
r := NewRetrier(10, 1, request)
err := r.RunRetry()
if err != nil {
// retry failed
}
// retry success
}