Documentation
¶
Overview ¶
Package decorators provides the shared client-boundary resilience stack: one reusable builder that composes, from outermost to innermost,
Bulkhead → Retry → CircuitBreaker → Timeout → Tracing → Metrics → Logging
around any direct-SDK client operation (S3, SQS, Bedrock, …). It generalizes the pattern proven in clients/storage/decorators (which hand-rolled CB/Timeout/Tracing/Metrics/Logging): each per-client decorator delegates its per-operation work to Run, so every direct-SDK client gets the same protection the RPC/gRPC/repo paths already have.
The layers are nil-able: a nil primitive (or zero timeout) disables that layer, so a bare Stack is a passthrough. Which layers a client gets is chosen by configuration (StackFromConfig); resilience primitives come from go/foundation/resilience behind go/core/interfaces.
Index ¶
- func Run[T any](ctx context.Context, s *Stack, op string, opts RunOpts, ...) (T, error)
- func RunStream[T any](ctx context.Context, s *Stack, op string, fn func(context.Context) (T, error)) (val T, cleanup func(), err error)
- type Config
- type Deps
- type RunOpts
- type Stack
- func (s *Stack) WithBulkhead(b interfaces.Bulkhead) *Stack
- func (s *Stack) WithCircuitBreaker(cb interfaces.CircuitBreaker) *Stack
- func (s *Stack) WithLogger(l interfaces.Logger) *Stack
- func (s *Stack) WithMetrics(m interfaces.Metrics) *Stack
- func (s *Stack) WithRetrier(r interfaces.Retrier) *Stack
- func (s *Stack) WithTimeout(d time.Duration) *Stack
- func (s *Stack) WithTracer(t oteltrace.Tracer) *Stack
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Run ¶
func Run[T any]( ctx context.Context, s *Stack, op string, opts RunOpts, fn func(context.Context) (T, error), ) (T, error)
Run executes fn through the stack in the order Bulkhead → Retry → CircuitBreaker → Timeout → Tracing → Metrics → Logging.
The nesting is load-bearing: Retry is OUTSIDE the circuit breaker, so each retry attempt passes through the breaker (it observes per-attempt outcomes and, once open, short-circuits the remaining attempts without invoking fn). The bulkhead holds its slot for the whole retry sequence. It is a free function because Go methods cannot be generic; the result is captured across attempts and the last attempt's value is returned.
func RunStream ¶
func RunStream[T any]( ctx context.Context, s *Stack, op string, fn func(context.Context) (T, error), ) (val T, cleanup func(), err error)
RunStream runs fn through Bulkhead → CircuitBreaker → Timeout → Tracing → Metrics → Logging for a lazy-stream operation whose returned value must outlive this call (e.g. an S3 GetObject body read AFTER the method returns). Unlike Run, the timeout and span are NOT ended on return — the caller must invoke the returned cleanup exactly once when the stream is closed, so the deadline stays alive for the whole transfer. Retry is never applied to a stream (the body cannot be replayed). On error, cleanup is invoked before returning.
Types ¶
type Config ¶
type Config struct {
// CircuitBreaker tunes the per-attempt breaker; its Name defaults to the client name.
CircuitBreaker circuitbreaker.Config `yaml:"circuit_breaker" mapstructure:"circuit_breaker"`
// Retry tunes the Retry layer; only consumed when RetryEnabled is true.
Retry retry.Config `yaml:"retry" mapstructure:"retry"`
// Bulkhead tunes the concurrency limit (the outermost layer).
Bulkhead bulkhead.Config `yaml:"bulkhead" mapstructure:"bulkhead"`
// Timeout is the per-attempt deadline; zero leaves the timeout layer off.
Timeout time.Duration `yaml:"timeout" mapstructure:"timeout"`
// Enabled gates the whole stack; false yields a bare passthrough (backwards-compatible).
Enabled bool `yaml:"enabled" mapstructure:"enabled"`
// RetryEnabled opts the client into the Retry layer (off by default so it never
// doubles up with an SDK/existing retry authority).
RetryEnabled bool `yaml:"retry_enabled" mapstructure:"retry_enabled"`
}
Config tunes the client resilience stack for one client tier. It is the minimal per-client surface (expands it): Enabled gates the whole stack, and the primitive sub-configs tune each layer. A Config with Enabled=false yields a bare (passthrough) stack — the backwards-compatible path.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns sensible production defaults: the stack enabled with Bulkhead/CircuitBreaker/Timeout on, and the Retry layer off (opt-in per client so it never doubles up with an SDK/existing retry authority).
type Deps ¶
type Deps struct {
// Metrics records per-operation counts, errors, and latency.
Metrics interfaces.Metrics
// Logger records failed operations.
Logger interfaces.Logger
// Tracer emits a span per operation.
Tracer oteltrace.Tracer
}
Deps carries the injected observability collaborators. Any may be nil, which leaves that layer off.
type RunOpts ¶
type RunOpts struct {
// Retryable enables the Retry layer for this operation. Body- or
// stream-consuming and non-idempotent operations (e.g. S3 Upload,
// CompleteMultipartUpload) must pass false so they are never replayed.
Retryable bool
}
RunOpts are the per-operation flags for Run.
type Stack ¶
type Stack struct {
// contains filtered or unexported fields
}
Stack composes resilience + observability layers around a client boundary. A nil layer (or zero timeout) is skipped, so the zero-value Stack is a passthrough. Build one with New + the With* options, or StackFromConfig.
func StackFromConfig ¶
StackFromConfig builds a Stack for the client named name from cfg and deps. A disabled config returns a bare passthrough stack (no layers). It fails loudly if a primitive rejects its config.
func (*Stack) WithBulkhead ¶
func (s *Stack) WithBulkhead(b interfaces.Bulkhead) *Stack
WithBulkhead adds the outermost concurrency-limiting layer.
func (*Stack) WithCircuitBreaker ¶
func (s *Stack) WithCircuitBreaker( cb interfaces.CircuitBreaker, ) *Stack
WithCircuitBreaker adds the circuit-breaker layer, evaluated per attempt.
func (*Stack) WithLogger ¶
func (s *Stack) WithLogger(l interfaces.Logger) *Stack
WithLogger logs each failed operation with its op name and error.
func (*Stack) WithMetrics ¶
func (s *Stack) WithMetrics(m interfaces.Metrics) *Stack
WithMetrics records per-operation counts, errors, and latency. A nil Metrics leaves the metrics layer off.
func (*Stack) WithRetrier ¶
func (s *Stack) WithRetrier(r interfaces.Retrier) *Stack
WithRetrier adds the Retry layer, applied only to Retryable operations.
func (*Stack) WithTimeout ¶
WithTimeout sets the per-attempt deadline; zero leaves the timeout layer off.