idempotencycfg

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: AGPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package idempotencycfg assembles an idempotency.Manager from environment configuration.

It selects nothing itself. What it configures is two other seams — a cachecfg.Config for the record store and a distributedlockcfg.Config for the lock guarding the claim — and the guarantee holds only if both are chosen for a fleet: the memory cache is per-process, so replicas would not see each other's records, and the noop locker acquires unconditionally, which leaves replay working while quietly removing mutual exclusion.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewManager

func NewManager[T any](
	ctx context.Context,
	cfg *Config,
	db database.Client,
	opts ...Option,
) (*idempotency.Manager[T], error)

NewManager builds a Manager for T from configuration.

T must be supplied explicitly — NewManager[Receipt](...) — because this constructor builds the record store itself, so nothing in the argument list mentions T. That single annotation is the whole cost: idempotency.Option carries no type parameter, so the options passed here need none.

db is required only when the lock provider is postgres; pass nil otherwise.

The transport adapters have their own NewManager, and callers wiring up HTTP or gRPC should prefer those: they apply the recordable rule for that transport, which this one cannot know. Reach for this when the guarded work is neither — a job runner, a queue consumer.

func RegisterManager

func RegisterManager[T any](i do.Injector)

RegisterManager registers an *idempotency.Manager[T] with the injector. It is generic because a Manager stores results of one concrete type; each result type the application replays is registered separately.

Prerequisites: *Config must be registered in the injector before the Manager is invoked. A database.Client is only required when the config's lock provider is postgres — which is what NewManager documents, and what this used to contradict by invoking one unconditionally: a redis-locked container that had registered no database panicked at build.

Types

type Config

type Config struct {

	// KeyPrefix namespaces store and lock keys.
	KeyPrefix string `env:"KEY_PREFIX" json:"keyPrefix,omitempty" yaml:"keyPrefix,omitempty"`
	// Lock configures the locker guarding the claim. It has no safe default —
	// the noop provider acquires unconditionally, which leaves replay working
	// while quietly removing mutual exclusion.
	Lock distributedlockcfg.Config `env:",init" envPrefix:"LOCK_" json:"lock,omitzero" yaml:"lock,omitempty"`

	// Cache configures the record store. Use the redis provider in
	// production: the memory provider is per-process, so replicas would not
	// see each other's records, and it holds a long TTL entirely in this
	// process's heap.
	Cache cachecfg.Config `env:",init" envPrefix:"CACHE_" json:"cache,omitzero" yaml:"cache,omitempty"`
	// TTL is how long a completed record stays replayable.
	TTL time.Duration `env:"TTL" json:"ttl,omitempty" yaml:"ttl,omitempty"`
	// InFlightTTL bounds how long a claim survives without completing. It is a
	// deadline for the guarded work, not a tuning knob: set it above the worst
	// case, since anything slower can produce a duplicate effect.
	InFlightTTL time.Duration `env:"IN_FLIGHT_TTL" json:"inFlightTTL,omitempty" yaml:"inFlightTTL,omitempty"`
	// MaxKeyLength is the longest client key accepted.
	MaxKeyLength int `env:"MAX_KEY_LENGTH" json:"maxKeyLength,omitempty" yaml:"maxKeyLength,omitempty"`
	// FailOpen runs the work when the record store cannot be read, instead of
	// refusing the request. It trades the guarantee for availability and is
	// wrong wherever a duplicate effect costs money — see
	// idempotency.StoreFailurePolicy.
	FailOpen bool `env:"FAIL_OPEN" json:"failOpen,omitempty" yaml:"failOpen,omitempty"`
	// contains filtered or unexported fields
}

Config assembles an idempotency.Manager from environment configuration.

func (*Config) EnsureDefaults

func (cfg *Config) EnsureDefaults()

EnsureDefaults fills in zero fields.

func (*Config) ValidateWithContext

func (cfg *Config) ValidateWithContext(ctx context.Context) error

ValidateWithContext validates a Config struct.

The nested configs are validated through validation.By closures because ozzo dereferences a struct-value field before checking ValidatableWithContext, so it would otherwise skip them.

type Option

type Option func(*options)

Option configures how NewManager assembles its manager.

The observability dependencies are options rather than parameters because every one of them is genuinely optional: an absent logger logs nowhere, an absent tracer provider traces nowhere, and an absent metrics provider records nothing. Requiring them positionally made a caller that wanted none of the three name all three anyway, usually as noops.

func WithLogger

func WithLogger(logger logging.Logger) Option

WithLogger attaches a logger. An absent logger logs nowhere.

func WithManagerOptions

func WithManagerOptions(opts ...idempotency.Option) Option

WithManagerOptions passes opts to the Manager, which applies them after the options it derives from configuration — so a caller can override anything.

They cannot be a second variadic on NewManager: Go allows one per function, and that slot is what makes the observability optional.

func WithMetricsProvider

func WithMetricsProvider(metricsProvider metrics.Provider) Option

WithMetricsProvider attaches a metrics provider. An absent provider records nothing.

func WithPillars

func WithPillars(p *observability.Pillars) Option

WithPillars attaches a logger, tracer provider, and metrics provider in one go, for the common case where a caller has already built them together. A nil Pillars attaches nothing.

It is applied in order with the individual options, so a caller can hand over its pillars and then override one of them.

func WithTracerProvider

func WithTracerProvider(tracerProvider tracing.Provider) Option

WithTracerProvider attaches a tracer provider, enabling spans on the instrumented operations. An absent tracer provider traces nowhere.

Jump to

Keyboard shortcuts

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