Documentation
¶
Overview ¶
Package resilience provides generic retry and circuit breaker patterns for building resilient clients. It supports any request/response type using Go generics and integrates with jp-go-errors for standardized error handling.
Example (CombineRetryAndCircuitBreaker) ¶
Example_combineRetryAndCircuitBreaker demonstrates using both retry and circuit breaker together.
// Create a mock client
client := &mockClient{
executeFunc: func(ctx context.Context, req string) (string, error) {
return "success", nil
},
}
// Combine retry and circuit breaker with default configs
combined := resilience.CombineRetryAndCircuitBreaker(
client,
resilience.DefaultRetryConfig(),
resilience.DefaultCircuitBreakerConfig(),
slog.Default(),
)
// Execute request with both retry and circuit breaker protection
ctx := context.Background()
resp, err := combined.Execute(ctx, "test request")
if err != nil {
fmt.Printf("Request failed: %v\n", err)
return
}
fmt.Printf("Response: %s\n", resp)
Output: Response: success
Example (CustomConfiguration) ¶
Example_customConfiguration demonstrates custom retry and circuit breaker configuration.
client := &mockClient{
executeFunc: func(ctx context.Context, req string) (string, error) {
return "success", nil
},
}
// Custom retry configuration
retryConfig := &resilience.RetryConfig{
MaxAttempts: 5,
Strategy: resilience.RetryStrategyExponential,
InitialDelay: 100 * time.Millisecond,
MaxDelay: 5 * time.Second,
}
// Custom circuit breaker configuration
cbConfig := &resilience.CircuitBreakerConfig{
MaxRequests: 5,
Interval: 10 * time.Second,
Timeout: 60 * time.Second,
ReadyToTrip: func(counts resilience.CircuitBreakerCounts) bool {
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 10 && failureRatio >= 0.5
},
}
combined := resilience.CombineRetryAndCircuitBreaker(
client,
retryConfig,
cbConfig,
slog.Default(),
)
ctx := context.Background()
resp, err := combined.Execute(ctx, "test")
if err != nil {
fmt.Printf("Failed: %v\n", err)
return
}
fmt.Printf("Success: %s\n", resp)
Output: Success: success
Index ¶
- func NewStatusCodeError(statusCode int, err error) error
- type CircuitBreakerConfig
- type CircuitBreakerCounts
- type CircuitBreakerErrorClassifier
- type CircuitBreakerOption
- func WithCircuitBreakerErrorClassifier(classifier CircuitBreakerErrorClassifier) CircuitBreakerOption
- func WithCircuitBreakerLogger(logger *slog.Logger) CircuitBreakerOption
- func WithInterval(interval time.Duration) CircuitBreakerOption
- func WithMaxRequests(maxRequests uint32) CircuitBreakerOption
- func WithReadyToTrip(fn func(counts CircuitBreakerCounts) bool) CircuitBreakerOption
- func WithStateChangeHandler(fn func(name string, from, to CircuitBreakerState)) CircuitBreakerOption
- func WithTimeout(timeout time.Duration) CircuitBreakerOption
- type CircuitBreakerState
- type CircuitBreakerWrapper
- func (w *CircuitBreakerWrapper[Req, Resp]) Counts() CircuitBreakerCounts
- func (w *CircuitBreakerWrapper[Req, Resp]) Execute(ctx context.Context, req Req) (Resp, error)
- func (w *CircuitBreakerWrapper[Req, Resp]) GetHealth() HealthStatus
- func (w *CircuitBreakerWrapper[Req, Resp]) State() CircuitBreakerState
- type ErrorClassifier
- type HTTPError
- type HTTPStatusClassifier
- type HealthStatus
- type ResilientClient
- type RetryConfig
- type RetryOption
- func WithConstantBackoff(delay time.Duration) RetryOption
- func WithErrorClassifier(classifier ErrorClassifier) RetryOption
- func WithExponentialBackoff(initialDelay, maxDelay time.Duration) RetryOption
- func WithFibonacciBackoff(initialDelay, maxDelay time.Duration) RetryOption
- func WithMaxAttempts(attempts int) RetryOption
- func WithMultiplier(multiplier float64) RetryOption
- func WithRetryLogger(logger *slog.Logger) RetryOption
- type RetryStats
- type RetryStrategy
- type RetryWrapper
- type StatusCodeError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewStatusCodeError ¶
NewStatusCodeError creates a new StatusCodeError. This is useful when wrapping errors from systems that don't provide status codes.
Example:
err := doRequest()
if err != nil {
return resilience.NewStatusCodeError(http.StatusServiceUnavailable, err)
}
Types ¶
type CircuitBreakerConfig ¶
type CircuitBreakerConfig struct {
// ReadyToTrip is called with a copy of counts whenever a request fails in the closed state.
// If ReadyToTrip returns true, the circuit breaker will be placed into the open state.
// Default: trips after 3 requests with 60% failure rate
ReadyToTrip func(counts CircuitBreakerCounts) bool
// ErrorClassifier determines which errors should trip the circuit breaker.
// Default: HTTPStatusClassifier with standard trip codes
ErrorClassifier CircuitBreakerErrorClassifier
// OnStateChange is called whenever the circuit breaker changes state.
OnStateChange func(name string, from, to CircuitBreakerState)
// Logger for circuit breaker operations.
// Default: slog.Default()
Logger *slog.Logger
// Interval is the cyclic period of the closed state for the circuit breaker
// to clear the internal counts. If 0, never clears.
// Default: 10 seconds
Interval time.Duration
// Timeout is the period of the open state, after which the state becomes half-open.
// Default: 30 seconds
Timeout time.Duration
// MaxRequests is the maximum number of requests allowed to pass through
// when the circuit breaker is in the half-open state.
// Default: 3
MaxRequests uint32
}
CircuitBreakerConfig holds circuit breaker configuration options.
func DefaultCircuitBreakerConfig ¶
func DefaultCircuitBreakerConfig() *CircuitBreakerConfig
DefaultCircuitBreakerConfig returns circuit breaker configuration with sensible defaults.
type CircuitBreakerCounts ¶
type CircuitBreakerCounts struct {
Requests uint32
TotalSuccesses uint32
TotalFailures uint32
ConsecutiveSuccesses uint32
ConsecutiveFailures uint32
}
CircuitBreakerCounts holds the internal counts of the circuit breaker.
type CircuitBreakerErrorClassifier ¶
type CircuitBreakerErrorClassifier interface {
// ShouldTripCircuit returns true if the error represents a failure serious enough
// to open the circuit breaker and stop requests temporarily.
ShouldTripCircuit(err error) bool
}
CircuitBreakerErrorClassifier determines whether an error should trip the circuit breaker. Implement this interface to customize circuit breaker behavior for your specific error types.
func DefaultCircuitBreakerErrorClassifier ¶
func DefaultCircuitBreakerErrorClassifier() CircuitBreakerErrorClassifier
DefaultCircuitBreakerErrorClassifier provides reasonable defaults for circuit breaker tripping. It trips on authentication errors (401, 403) and server errors (5xx), but not on rate limits or timeouts which are transient.
type CircuitBreakerOption ¶
type CircuitBreakerOption func(*CircuitBreakerConfig)
CircuitBreakerOption is a functional option for configuring circuit breaker behavior.
func WithCircuitBreakerErrorClassifier ¶
func WithCircuitBreakerErrorClassifier(classifier CircuitBreakerErrorClassifier) CircuitBreakerOption
WithCircuitBreakerErrorClassifier sets a custom error classifier for circuit breaker decisions.
Example:
classifier := &MyCustomClassifier{}
resilience.WithCircuitBreakerErrorClassifier(classifier)
func WithCircuitBreakerLogger ¶
func WithCircuitBreakerLogger(logger *slog.Logger) CircuitBreakerOption
WithCircuitBreakerLogger sets a custom logger for circuit breaker operations.
Example:
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) resilience.WithCircuitBreakerLogger(logger)
func WithInterval ¶
func WithInterval(interval time.Duration) CircuitBreakerOption
WithInterval sets the interval for clearing counts in closed state.
Example:
resilience.WithInterval(10 * time.Second)
func WithMaxRequests ¶
func WithMaxRequests(maxRequests uint32) CircuitBreakerOption
WithMaxRequests sets the maximum number of requests in half-open state.
Example:
resilience.WithMaxRequests(5)
func WithReadyToTrip ¶
func WithReadyToTrip(fn func(counts CircuitBreakerCounts) bool) CircuitBreakerOption
WithReadyToTrip sets a custom function to determine when to trip the circuit.
Example:
resilience.WithReadyToTrip(func(counts resilience.CircuitBreakerCounts) bool {
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 5 && failureRatio >= 0.5
})
func WithStateChangeHandler ¶
func WithStateChangeHandler(fn func(name string, from, to CircuitBreakerState)) CircuitBreakerOption
WithStateChangeHandler sets a callback for circuit breaker state changes.
Example:
resilience.WithStateChangeHandler(func(name string, from, to resilience.CircuitBreakerState) {
log.Printf("Circuit %s changed from %s to %s", name, from, to)
})
func WithTimeout ¶
func WithTimeout(timeout time.Duration) CircuitBreakerOption
WithTimeout sets the timeout for staying in open state.
Example:
resilience.WithTimeout(60 * time.Second)
type CircuitBreakerState ¶
type CircuitBreakerState int
CircuitBreakerState represents the state of the circuit breaker.
const ( // StateClosed means the circuit is closed and requests flow normally. StateClosed CircuitBreakerState = iota // StateHalfOpen means the circuit is testing if the service has recovered. StateHalfOpen // StateOpen means the circuit is open and requests are rejected immediately. StateOpen )
func (CircuitBreakerState) String ¶
func (s CircuitBreakerState) String() string
String returns the string representation of the circuit breaker state.
type CircuitBreakerWrapper ¶
type CircuitBreakerWrapper[Req, Resp any] struct { // contains filtered or unexported fields }
CircuitBreakerWrapper wraps a ResilientClient with circuit breaker functionality. It tracks failures and opens the circuit when too many failures occur, preventing requests from reaching a failing downstream service.
func NewCircuitBreakerWrapper ¶
func NewCircuitBreakerWrapper[Req, Resp any]( client ResilientClient[Req, Resp], opts ...CircuitBreakerOption, ) *CircuitBreakerWrapper[Req, Resp]
NewCircuitBreakerWrapper creates a new circuit breaker wrapper around a ResilientClient. It applies the provided options to configure circuit breaker behavior.
Example:
wrapper := resilience.NewCircuitBreakerWrapper(
client,
resilience.WithMaxRequests(5),
resilience.WithTimeout(60*time.Second),
)
func (*CircuitBreakerWrapper[Req, Resp]) Counts ¶
func (w *CircuitBreakerWrapper[Req, Resp]) Counts() CircuitBreakerCounts
Counts returns the current counts of the circuit breaker.
func (*CircuitBreakerWrapper[Req, Resp]) Execute ¶
func (w *CircuitBreakerWrapper[Req, Resp]) Execute(ctx context.Context, req Req) (Resp, error)
Execute executes the request through the circuit breaker. If the circuit is open, requests are rejected immediately without calling the underlying client. Circuit breaker errors are wrapped with jperrors types for consistent error handling:
- gobreaker.ErrOpenState becomes jperrors.ErrCircuitOpen
- gobreaker.ErrTooManyRequests becomes jperrors.ErrCircuitTooManyRequests
func (*CircuitBreakerWrapper[Req, Resp]) GetHealth ¶
func (w *CircuitBreakerWrapper[Req, Resp]) GetHealth() HealthStatus
GetHealth returns the health status of the circuit breaker.
func (*CircuitBreakerWrapper[Req, Resp]) State ¶
func (w *CircuitBreakerWrapper[Req, Resp]) State() CircuitBreakerState
State returns the current state of the circuit breaker.
type ErrorClassifier ¶
type ErrorClassifier interface {
// IsRetryable returns true if the error represents a transient failure
// that should be retried.
IsRetryable(err error) bool
}
ErrorClassifier determines whether an error should trigger a retry. Implement this interface to customize retry behavior for your specific error types.
func DefaultErrorClassifier ¶
func DefaultErrorClassifier() ErrorClassifier
DefaultErrorClassifier provides reasonable defaults for most use cases. It treats 5xx errors, 429 (rate limit), network errors, and timeouts as retryable. It trips the circuit on authentication errors and persistent server errors.
type HTTPError ¶
HTTPError represents an error with an associated HTTP status code. Many HTTP client libraries provide errors that implement this interface.
type HTTPStatusClassifier ¶
type HTTPStatusClassifier struct {
// RetryableStatuses lists HTTP status codes that should trigger retries.
// Defaults to 429, 500, 502, 503, 504 if nil.
RetryableStatuses []int
// CircuitTripStatuses lists HTTP status codes that should trip the circuit breaker.
// Defaults to 401, 403, 500, 502, 503, 504 if nil.
CircuitTripStatuses []int
}
HTTPStatusClassifier provides HTTP status code-based error classification. It classifies errors based on HTTP status codes, treating certain codes as retryable and others as circuit breaker trip conditions.
func NewHTTPStatusClassifier ¶
func NewHTTPStatusClassifier() *HTTPStatusClassifier
NewHTTPStatusClassifier creates a new HTTPStatusClassifier with default status code mappings. Retryable: 429 (rate limit), 500, 502, 503, 504 (server errors) Circuit trip: 401, 403 (auth errors), 500, 502, 503, 504 (server errors)
func (*HTTPStatusClassifier) IsRetryable ¶
func (c *HTTPStatusClassifier) IsRetryable(err error) bool
IsRetryable implements ErrorClassifier for HTTP status codes. It checks if the error has an HTTP status code that indicates a retryable condition.
func (*HTTPStatusClassifier) ShouldTripCircuit ¶
func (c *HTTPStatusClassifier) ShouldTripCircuit(err error) bool
ShouldTripCircuit implements CircuitBreakerErrorClassifier for HTTP status codes. It checks if the error has an HTTP status code that indicates the circuit should trip.
type HealthStatus ¶
type HealthStatus struct {
// Healthy indicates whether the circuit breaker is in a healthy state.
// True for closed and half-open states, false for open state.
Healthy bool `json:"healthy"`
// Status is a short string description of the state ("closed", "half-open", "open", "unknown").
Status string `json:"status"`
// State is the full string representation of the circuit breaker state.
State string `json:"state"`
// Requests is the total number of requests in the current interval.
Requests uint32 `json:"requests"`
// TotalSuccesses is the total number of successful requests.
TotalSuccesses uint32 `json:"total_successes"`
// TotalFailures is the total number of failed requests.
TotalFailures uint32 `json:"total_failures"`
// ConsecutiveFailures is the number of consecutive failures.
ConsecutiveFailures uint32 `json:"consecutive_failures"`
// ConsecutiveSuccesses is the number of consecutive successes.
ConsecutiveSuccesses uint32 `json:"consecutive_successes"`
}
HealthStatus represents the health status of a circuit breaker. It provides a strongly-typed alternative to map[string]interface{} for health checks.
type ResilientClient ¶
type ResilientClient[Req, Resp any] interface { // Execute performs a request and returns a response or error. // The context should be used to control timeouts and cancellation. Execute(ctx context.Context, req Req) (Resp, error) }
ResilientClient defines a generic interface for executing requests with retry and circuit breaker support. Type parameters Req and Resp can be any types, making this suitable for HTTP clients, gRPC clients, database clients, or any other operation that needs resilience patterns.
Example:
type HTTPClient struct {
client *http.Client
}
func (c *HTTPClient) Execute(ctx context.Context, req *http.Request) (*http.Response, error) {
return c.client.Do(req.WithContext(ctx))
}
// Wrap with retry
resilientClient := resilience.NewRetryWrapper(
httpClient,
resilience.WithMaxAttempts(3),
resilience.WithExponentialBackoff(time.Second, 30*time.Second),
)
func CombineRetryAndCircuitBreaker ¶
func CombineRetryAndCircuitBreaker[Req, Resp any]( client ResilientClient[Req, Resp], retryConfig *RetryConfig, cbConfig *CircuitBreakerConfig, logger *slog.Logger, ) ResilientClient[Req, Resp]
CombineRetryAndCircuitBreaker creates a wrapper with both retry and circuit breaker functionality. The circuit breaker is applied first (inner layer) to protect the underlying service, then retry logic is applied (outer layer) to handle transient failures. This layering ensures circuit breaker state is accurately maintained while providing resilience.
type RetryConfig ¶
type RetryConfig struct {
// ErrorClassifier determines which errors should trigger retries.
// Default: HTTPStatusClassifier with standard retryable codes
ErrorClassifier ErrorClassifier
// Logger for retry operations.
// Default: slog.Default()
Logger *slog.Logger
// Strategy defines the backoff strategy.
// Default: RetryStrategyExponential
Strategy RetryStrategy
// InitialDelay is the delay before the first retry.
// Default: 1 second
InitialDelay time.Duration
// MaxDelay is the maximum delay between retries (for exponential/fibonacci).
// Default: 30 seconds
MaxDelay time.Duration
// Multiplier is the backoff multiplier for exponential strategy.
// For exponential backoff, delay = initialDelay * (multiplier ^ attempt).
// Default: 2.0 (doubling)
// Common values: 1.5 (moderate growth), 2.0 (doubling), 3.0 (aggressive growth)
Multiplier float64
// MaxAttempts is the maximum number of attempts (including the initial request).
// Default: 3
MaxAttempts int
}
RetryConfig holds retry configuration options.
func DefaultRetryConfig ¶
func DefaultRetryConfig() *RetryConfig
DefaultRetryConfig returns retry configuration with sensible defaults.
type RetryOption ¶
type RetryOption func(*RetryConfig)
RetryOption is a functional option for configuring retry behavior.
func WithConstantBackoff ¶
func WithConstantBackoff(delay time.Duration) RetryOption
WithConstantBackoff configures constant delay between retries with jitter. All retry delays will be approximately the same.
Example:
resilience.WithConstantBackoff(2 * time.Second) // Delays: ~2s, ~2s, ~2s, ~2s
func WithErrorClassifier ¶
func WithErrorClassifier(classifier ErrorClassifier) RetryOption
WithErrorClassifier sets a custom error classifier for retry decisions.
Example:
classifier := &MyCustomClassifier{}
resilience.WithErrorClassifier(classifier)
func WithExponentialBackoff ¶
func WithExponentialBackoff(initialDelay, maxDelay time.Duration) RetryOption
WithExponentialBackoff configures exponential backoff with jitter. Each retry delay is multiplied by the configured multiplier (default 2.0) up to maxDelay.
Example:
resilience.WithExponentialBackoff(time.Second, 30*time.Second) // With default multiplier 2.0: ~1s, ~2s, ~4s, ~8s, ~16s, 30s (capped)
func WithFibonacciBackoff ¶
func WithFibonacciBackoff(initialDelay, maxDelay time.Duration) RetryOption
WithFibonacciBackoff configures fibonacci backoff with jitter. Delays follow the fibonacci sequence up to maxDelay.
Example:
resilience.WithFibonacciBackoff(time.Second, 30*time.Second) // Delays: ~1s, ~1s, ~2s, ~3s, ~5s, ~8s, ~13s, ~21s, 30s (capped)
func WithMaxAttempts ¶
func WithMaxAttempts(attempts int) RetryOption
WithMaxAttempts sets the maximum number of retry attempts. The total number of calls will be MaxAttempts (including the initial attempt).
Example:
resilience.WithMaxAttempts(5) // Try up to 5 times total
func WithMultiplier ¶
func WithMultiplier(multiplier float64) RetryOption
WithMultiplier sets the backoff multiplier for exponential strategy. Only applies when using RetryStrategyExponential.
Example:
resilience.WithMultiplier(1.5) // 50% growth per retry // With InitialDelay=1s: ~1s, ~1.5s, ~2.25s, ~3.375s, ...
func WithRetryLogger ¶
func WithRetryLogger(logger *slog.Logger) RetryOption
WithRetryLogger sets a custom logger for retry operations.
Example:
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) resilience.WithRetryLogger(logger)
type RetryStats ¶
type RetryStats struct {
// TotalAttempts is the total number of attempts made (including initial and retries)
TotalAttempts int64
// TotalRetries is the number of retry attempts (not including initial attempts)
TotalRetries int64
// TotalSuccesses is the number of successful operations
TotalSuccesses int64
// TotalFailures is the number of failed operations (after all retries exhausted)
TotalFailures int64
// LastAttemptTime is the time of the last attempt
LastAttemptTime time.Time
// LastError is the last error encountered (if any)
LastError error
}
RetryStats holds statistics about retry operations.
type RetryStrategy ¶
type RetryStrategy string
RetryStrategy defines the backoff strategy for retry operations.
const ( // RetryStrategyExponential uses exponential backoff with jitter. RetryStrategyExponential RetryStrategy = "exponential" // RetryStrategyConstant uses a constant delay between retries with jitter. RetryStrategyConstant RetryStrategy = "constant" // RetryStrategyFibonacci uses fibonacci backoff with jitter. RetryStrategyFibonacci RetryStrategy = "fibonacci" )
type RetryWrapper ¶
type RetryWrapper[Req, Resp any] struct { // contains filtered or unexported fields }
RetryWrapper wraps a ResilientClient with configurable retry logic. It uses exponential, constant, or fibonacci backoff strategies with jitter to prevent thundering herd problems.
func NewRetryWrapper ¶
func NewRetryWrapper[Req, Resp any]( client ResilientClient[Req, Resp], opts ...RetryOption, ) *RetryWrapper[Req, Resp]
NewRetryWrapper creates a new retry wrapper around a ResilientClient. It applies the provided options to configure retry behavior.
Example:
wrapper := resilience.NewRetryWrapper(
client,
resilience.WithMaxAttempts(5),
resilience.WithExponentialBackoff(time.Second, 30*time.Second),
)
func (*RetryWrapper[Req, Resp]) Execute ¶
func (w *RetryWrapper[Req, Resp]) Execute(ctx context.Context, req Req) (Resp, error)
Execute performs the request with retry logic. It will retry on retryable errors up to MaxAttempts times using the configured backoff strategy.
func (*RetryWrapper[Req, Resp]) GetRetryStats ¶
func (w *RetryWrapper[Req, Resp]) GetRetryStats() RetryStats
GetRetryStats returns statistics about retry operations. This method is thread-safe and returns a snapshot of the current statistics.
type StatusCodeError ¶
StatusCodeError wraps an error with an HTTP status code. Use this when you need to add status code information to an existing error.
func (*StatusCodeError) Error ¶
func (e *StatusCodeError) Error() string
Error implements the error interface.
func (*StatusCodeError) StatusCode ¶
func (e *StatusCodeError) StatusCode() int
StatusCode returns the HTTP status code. This implements the HTTPError interface.
func (*StatusCodeError) Unwrap ¶
func (e *StatusCodeError) Unwrap() error
Unwrap implements error unwrapping for errors.Is and errors.As.