resilium

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 12, 2026 License: MIT Imports: 8 Imported by: 0

README

resilium

Composable resilience policies for Go — retry, circuit breaker, timeout, and rate limiting, unified behind one type-safe API.

Go Reference Go Report Card CI

Why resilium?

Most Go projects end up hand-wiring retry logic, circuit breakers, and timeouts separately — often with inconsistent behavior, no shared observability, and no clear execution order between them. resilium fixes that with a single composable pipeline:

policy := resilium.New(
    resilium.WithTimeout(5*time.Second),
    resilium.WithRetry(retry.Config{
        MaxAttempts: 3,
        Backoff:     retry.ExponentialBackoff(100*time.Millisecond, 2*time.Second),
    }),
    resilium.WithCircuitBreaker(circuitbreaker.Config{
        Name:             "user-service",
        FailureThreshold: 0.5,
        MinRequests:      10,
        OpenDuration:     30 * time.Second,
        WindowSize:       50, // defaults to 20 if unset
    }),
)

user, err := resilium.Execute(ctx, policy, func(ctx context.Context) (User, error) {
    return fetchUser(ctx, userID)
})

No wrapper soup, no interface{} juggling — results are fully typed via generics.

Features

  • Composable policies — combine retry, circuit breaker, timeout, and rate limiting in any order; each works standalone too
  • Generics-firstExecute[T] returns your actual type, not interface{}
  • Context-aware — cancellation and deadlines propagate correctly through every policy
  • Observable by default — structured logging hooks and OpenTelemetry metrics integration included, not bolted on
  • Zero required dependencies — the core module has no third-party dependencies; OpenTelemetry support is an optional submodule

Installation

go get github.com/sinashahoveisi/resilium

Requires Go 1.22 or later (uses generics and the standard log/slog package).

Quick start

The canonical runnable example lives at examples/basic/main.go:

go run ./examples/basic

It combines retry, timeout, and rate limiting:

policy := resilium.New(
    resilium.WithRetry(retry.Config{
        MaxAttempts: 3,
        Backoff:     retry.ExponentialBackoff(100*time.Millisecond, 1*time.Second),
    }),
    resilium.WithTimeout(2*time.Second),
    resilium.WithRateLimit(10, 10), // 10 req/s sustained, burst up to 10
)
result, err := resilium.Execute(ctx, policy, callFlakyService)

See examples/ for additional circuit breaker and combined-policy examples.

Policy execution order

When you combine policies, order matters. resilium applies them in the order you list them — outermost first:

resilium.New(
    resilium.WithTimeout(5*time.Second),   // outermost: bounds total time, including retries
    resilium.WithRetry(retryConfig),       // retries the call below on failure
    resilium.WithCircuitBreaker(cbConfig), // innermost: guards the actual call
)

This is deliberate rather than automatic, because the "correct" order depends on your use case (e.g. do you want a circuit breaker to see every retry attempt, or just the overall outcome?). See docs/policy-order.md for ordering examples and docs/guide.md for threshold tuning and common pitfalls.

Note: the circuit breaker evaluates failures over a sliding window of the last WindowSize requests (default 20), not a cumulative all-time counter. This means a long-running healthy service that suddenly starts failing trips the breaker within WindowSize failures — not after thousands of accumulated historical successes dilute the ratio.

Observability

policy := resilium.New(
    resilium.WithLogger(slog.Default()),
    resilium.WithHooks(resilium.Hooks{
        OnRetry:       func(attempt int, err error) { /* ... */ },
        OnCircuitOpen: func(name string) { /* ... */ },
    }),
)

OpenTelemetry metrics are available via the optional resilium/otel submodule:

import otelresilium "github.com/sinashahoveisi/resilium/otel"

policy := resilium.New(
    resilium.WithHooks(otelresilium.Metrics(otel.Meter("my-service"))),
)

See otel/README.md. The core module stays dependency-free.

Status

resilium is under active development. The API may change before v1.0. See CHANGELOG.md for release notes, ROADMAP.md for what's planned, and docs/versioning.md for the semver policy that takes effect at v1.0.0.

Comparison with alternatives

resilium sony/gobreaker avast/retry-go failsafe-go
Generics
Circuit breaker
Retry
Composable policies
Built-in OTel metrics
Zero core dependencies

Performance microbenchmarks vs gobreaker and retry-go (hot-path overhead only) are in benchmarks/README.md. The table above compares features, not ns/op.

Contributing

Contributions are welcome. Please read CONTRIBUTING.md before opening a PR — it covers the development setup, testing requirements, and commit conventions.

License

MIT — see LICENSE.

Documentation

Overview

Package resilium provides composable resilience policies — retry, circuit breaker, timeout, and rate limiting — behind a single, type-safe execution API.

Policies are built with New and the With* option functions, then executed through Execute. Middleware order matters; see docs/policy-order.md.

Index

Constants

This section is empty.

Variables

View Source
var ErrCircuitOpen = errors.New("resilium: circuit breaker is open")

ErrCircuitOpen is returned when a call is rejected because its circuit breaker is in the open state. Use errors.Is to test for it; the underlying circuitbreaker.ErrCircuitOpen may also be present in the error chain when using the subpackage directly.

View Source
var ErrMaxAttemptsExceeded = errors.New("resilium: max retry attempts exceeded")

ErrMaxAttemptsExceeded is returned when retry attempts are exhausted without a successful result. The last underlying error is wrapped; errors.Is(err, retry.ErrMaxAttemptsExceeded) may also be true.

View Source
var ErrRateLimited = errors.New("resilium: rate limit exceeded")

ErrRateLimited is returned when a call is rejected by a rate-limit policy because no token was available. WithRateLimit never blocks waiting for a token.

View Source
var ErrTimeout = errors.New("resilium: operation timed out")

ErrTimeout is returned when an operation exceeds its configured timeout before completing. errors.Is(err, context.DeadlineExceeded) also returns true for timeout errors wrapped by WithTimeout.

Functions

func Execute

func Execute[T any](ctx context.Context, p *Policy, op Operation[T]) (T, error)

Execute runs op through every middleware configured on the policy and returns the typed result. It respects ctx cancellation throughout the middleware chain. Errors from middlewares are returned as-is (often wrapped sentinels such as ErrTimeout or ErrCircuitOpen); use errors.Is to inspect them.

Types

type Hooks

type Hooks struct {
	// OnRetry is called when a failed attempt will be retried. attempt is
	// 1-indexed (1 = first failure that triggers a retry). It is not
	// called on the final failed attempt when no retry follows.
	OnRetry func(attempt int, err error)
	// OnCircuitOpen is called when a circuit breaker transitions to open.
	// name is circuitbreaker.Config.Name when set via WithCircuitBreaker,
	// otherwise "".
	OnCircuitOpen func(name string)
	// OnCircuitClose is called when a circuit breaker transitions to closed
	// (typically after a successful half-open trial).
	OnCircuitClose func(name string)
	// OnTimeout is called when WithTimeout detects a deadline exceeded.
	OnTimeout func()
	// OnRateLimited is called when WithRateLimit rejects a call because
	// no token was available.
	OnRateLimited func()
}

Hooks lets callers observe policy events without wiring a full logger or metrics backend. Callbacks are invoked from the middleware that triggers them; they must not block for long. A Policy is safe for concurrent Execute calls; hook implementations should be thread-safe if they mutate shared state.

type Middleware

type Middleware func(next OperationFunc) OperationFunc

Middleware wraps an OperationFunc with additional behavior (retry, circuit breaking, timeout, etc.). Middlewares compose in the order given to New: the first With* option is outermost.

type Operation

type Operation[T any] func(ctx context.Context) (T, error)

Operation is the unit of work resilium executes. It is generic over the result type T so callers get their real type back, not interface{}.

type OperationFunc

type OperationFunc func(ctx context.Context) (any, error)

OperationFunc is the untyped form of Operation used internally so that middlewares can be composed without needing to know the result type.

type Option

type Option func(*Policy)

Option configures a Policy when passed to New.

func WithCircuitBreaker

func WithCircuitBreaker(cfg circuitbreaker.Config) Option

WithCircuitBreaker adds circuit-breaking behavior to the policy. Each Policy holds one CircuitBreaker instance shared across Execute calls on that policy; use separate policies (or circuitbreaker.Do with a shared breaker) for different dependencies. Set cfg.Name to identify the breaker in OnCircuitOpen, OnCircuitClose, and logger output. Open calls return ErrCircuitOpen.

func WithHooks

func WithHooks(h Hooks) Option

WithHooks attaches the given hooks to the policy, chaining with any hooks already registered (e.g. from WithLogger). Later registrations run after earlier ones for the same event.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger attaches structured logging to policy events (retries, circuit state transitions, timeouts, rate-limit rejections). A nil logger defaults to slog.Default(). Logging is implemented via Hooks merged with any hooks from WithHooks.

func WithRateLimit

func WithRateLimit(requestsPerSecond float64, burst int) Option

WithRateLimit bounds how often the wrapped operation may run using a token-bucket limiter. requestsPerSecond is the sustained refill rate; burst is the maximum number of tokens that can accumulate (allowing short bursts without rejecting). A typical starting point is burst equal to requestsPerSecond (rounded up) or a small fixed value such as 5–10. Rejected calls return ErrRateLimited immediately without blocking.

func WithRetry

func WithRetry(cfg retry.Config) Option

WithRetry adds retry behavior to the policy using the given config. When RetryIf is nil, retries stop immediately on ErrCircuitOpen so an open circuit breaker is not hammered through backoff cycles. Exhausted retries return ErrMaxAttemptsExceeded wrapping the last error.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout bounds execution time of the wrapped operation using context.WithTimeout. When the deadline is exceeded, returns ErrTimeout wrapping context.DeadlineExceeded. Parent context cancellation returns context.Canceled and is not mapped to ErrTimeout.

type Policy

type Policy struct {
	// contains filtered or unexported fields
}

Policy is an ordered, composable set of resilience behaviors. Construct one with New and the With* option functions. A Policy is safe for concurrent use: multiple goroutines may call Execute on the same Policy instance.

func New

func New(opts ...Option) *Policy

New builds a Policy from the given options, applied in the order listed. The first option is the outermost middleware at execution time. See docs/policy-order.md for guidance on sequencing retry, circuit breaker, timeout, and rate limiting.

Directories

Path Synopsis
Package circuitbreaker implements the circuit breaker pattern as a resilium middleware, usable standalone as well.
Package circuitbreaker implements the circuit breaker pattern as a resilium middleware, usable standalone as well.
examples
basic command
Command basic demonstrates using resilium's retry, timeout, and rate limiting policies to call a flaky operation.
Command basic demonstrates using resilium's retry, timeout, and rate limiting policies to call a flaky operation.
internal
ratelimit
Package ratelimit provides a minimal token-bucket rate limiter for internal use by resilium policies.
Package ratelimit provides a minimal token-bucket rate limiter for internal use by resilium policies.
otel module
Package retry provides retry policies with configurable backoff strategies, used as a resilium middleware but also usable standalone.
Package retry provides retry policies with configurable backoff strategies, used as a resilium middleware but also usable standalone.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL