Documentation
¶
Overview ¶
Package idempotency makes value-bearing RPCs safe to retry: on first request the handler runs and the response is stored under the client-supplied key; on replay with the same key + same request hash the cached response is returned without re-invoking the handler.
Concurrent same-key requests block on the in-flight Reserve and reuse the eventual cached response, so duplicate writes from racing retries collapse to a single handler invocation.
Backends behind the Store interface: in-memory (tests), Redis (production, fast), Postgres (production, durable).
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrHashMismatch = errors.New("idempotency: request hash differs for cached key") ErrWaitTimeout = errors.New("idempotency: timed out waiting for in-flight key") )
Errors surfaced by the middleware.
Functions ¶
Types ¶
type Config ¶
type Config struct {
Store Store
// DefaultTTL is how long records live. Default 24h.
DefaultTTL time.Duration
// HeaderName is the inbound header containing the idempotency key.
// Default "Idempotency-Key". Requests without the header bypass
// the middleware entirely (treated as non-idempotent).
HeaderName string
// RequestHash derives the request hash. Default: SHA-256 over
// method + path + body bytes.
RequestHash func(*http.Request) (string, error)
// WaitTimeout caps how long a concurrent same-key request blocks
// on the in-flight reservation. Default 30s.
WaitTimeout time.Duration
}
Config configures the RESTMiddleware.
type MemoryConfig ¶
MemoryConfig configures a MemoryStore.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-process Store suited for tests and single-instance services. Records are pruned on access (lazy expiry) — there is no background goroutine.
func NewMemoryStore ¶
func NewMemoryStore(cfg MemoryConfig) *MemoryStore
NewMemoryStore constructs an empty MemoryStore.
type Store ¶
type Store interface {
// Reserve atomically claims `key` for the supplied request hash.
//
// Returns (existing, false, nil) when a record already exists for
// `key` — the caller serves `existing.Body` directly.
//
// Returns (nil, true, nil) when the key is fresh — the caller
// invokes the handler, then calls Commit to store the response.
//
// Errors are transport / store failures; the caller should bubble
// them as 5xx.
Reserve(ctx context.Context, key string, requestHash string, ttl time.Duration) (existing *Cached, fresh bool, err error)
// Commit writes the final response under `key`. Idempotent — a
// second Commit for the same key with a matching hash is a no-op.
Commit(ctx context.Context, key string, requestHash string, response Cached) error
// Release drops a reservation without committing a response. Used
// when the handler fails in a way that should not be cached (5xx,
// panic, transport hangup) — subsequent retries should be free to
// run the handler again. Idempotent.
Release(ctx context.Context, key string) error
}
Store persists idempotency records. Implementations must be goroutine-safe.