idempotency

package
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Jun 1, 2026 License: MIT Imports: 8 Imported by: 0

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

View Source
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

func RESTMiddleware

func RESTMiddleware(cfg Config) func(http.Handler) http.Handler

RESTMiddleware returns an HTTP middleware that enforces idempotency on requests carrying the configured header. Requests without the header pass through unchanged.

Types

type Cached

type Cached struct {
	Status      int
	Body        []byte
	RequestHash string
	StoredAt    time.Time
}

Cached is the stored response for a previously-completed request.

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

type MemoryConfig struct {
	Clock func() time.Time
}

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.

func (*MemoryStore) Commit

func (s *MemoryStore) Commit(_ context.Context, key, requestHash string, response Cached) error

Commit implements Store.

func (*MemoryStore) Release

func (s *MemoryStore) Release(_ context.Context, key string) error

Release implements Store.

func (*MemoryStore) Reserve

func (s *MemoryStore) Reserve(ctx context.Context, key, requestHash string, ttl time.Duration) (*Cached, bool, error)

Reserve implements Store.

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.

Jump to

Keyboard shortcuts

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