Skip to content

feat: Reserver interface + custom Storage example & Redis/Valkey guide#12

Merged
christiangda merged 2 commits into
mainfrom
docs/custom-storage-guide
Jul 11, 2026
Merged

feat: Reserver interface + custom Storage example & Redis/Valkey guide#12
christiangda merged 2 commits into
mainfrom
docs/custom-storage-guide

Conversation

@christiangda

@christiangda christiangda commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What

Two related things that make both extension points — bring-your-own store and bring-your-own limiter/middleware — first-class and well-documented.

1. New optional Reserver / Reservation interfaces (source feature)

Custom limiters (e.g. a Redis/Valkey-backed one) previously couldn't produce accurate Retry-After / RateLimit-Reset headers: the middleware had to downcast to the concrete *rate.Limiter and fell back to Allow-only for anything else (old code). This adds a backend-agnostic reservation seam.

  • reservation.goReserver (Reserve() Reservation) and Reservation (OK/Delay/Cancel), mirroring the useful subset of *rate.Reservation. Both optional; feature-detected via type assertion.
  • rate.goNewRateLimiterFunc now returns RateLimiter, a thin wrapper embedding *rate.Limiter (promoting Allow/Wait/Burst/TokensAt) that also implements Reserver.
  • examples/middleware — now feature-detects Reserver (works with any backend) and enriches RateLimit-Remaining opportunistically.
  • rate_test.go — covers detection, consume/cancel, and delay.

BREAKING: code that type-asserts manager.GetOrAdd(key) to *rate.Limiter must now assert ratelimiter.Reserver (preferred) or ratelimiter.RateLimiter. Allow/Wait/Burst usage is unchanged. Documented in docs/MIGRATION.md.

2. Custom Storage example + distributed guide (docs)

  • examples/customstorage — runnable, goroutine-safe size-bounded LRU store (go run ./examples/customstorage -cap 2).
  • docs/CUSTOM_STORAGE.md — full guide: method contracts, LoadOrStore atomicity + a race test, why Storage is in-process only, and the correct distributed pattern (a Valkey/Redis-backed Limiter + token-bucket Lua, now also implementing Reserver), wired through a Storage resolver. Uses valkey-go.

Docs kept in sync

doc.go, limiter.go, README (features, core-concepts table, Allow/Wait/Reserve), docs/TOKEN_BUCKET.md, docs/CUSTOM_STORAGE.md, and docs/MIGRATION.md all updated. Mermaid diagrams included.

Verification

  • gofmt clean, go build ./..., go vet ./..., go test -race ./... all pass
  • both examples run; go doc renders the new symbols

The Valkey/Redis snippets are clearly labeled illustrative and are not compiled into the module (no valkey-go dependency added to go.mod).

🤖 Generated with Claude Code

christiangda and others added 2 commits July 11, 2026 15:49
The Storage[K, V] interface is already fully pluggable — NewBucketLimiter
accepts any implementation and InMemoryStorage is merely the default — but the
repo had no runnable example of a custom store and no guidance on the common
"can I back this with Redis?" question.

- examples/customstorage: a runnable, goroutine-safe, size-bounded LRU store
  that caps the number of live keys, demonstrating BYO Storage end to end.
- docs/CUSTOM_STORAGE.md: a full guide covering the method contracts (and which
  ones the manager actually calls), LoadOrStore atomicity, how to test it, and
  the crucial distinction that Storage is in-process only. It then documents the
  correct pattern for distributed limiting: a Valkey/Redis-backed Limiter (with
  a token-bucket Lua script and valkey-go usage) wired through a Storage acting
  as a key->limiter resolver.
- README: expand the Custom storage section to link both.

Docs/example only; no library source or public API changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-After

A custom Limiter (e.g. a Redis/Valkey-backed one) previously could not drive
accurate Retry-After / RateLimit-Reset headers: the middleware had to downcast
to the concrete *rate.Limiter to reach the Reserve API, and fell back to
Allow-only for anything else. This adds a backend-agnostic reservation seam so
any Limiter can be a first-class citizen of header-accurate middleware.

- reservation.go: new optional Reserver interface (Reserve() Reservation) and
  Reservation interface (OK/Delay/Cancel), mirroring the useful subset of
  *rate.Reservation.
- rate.go: NewRateLimiterFunc now returns RateLimiter, a thin wrapper that
  embeds *rate.Limiter (promoting Allow/Wait/Burst/TokensAt) and implements
  Reserver. Compile-time assertions included.
- examples/middleware: feature-detect Reserver instead of downcasting to
  *rate.Limiter; enrich RateLimit-Remaining opportunistically via RateLimiter.
- rate_test.go: cover Reserver detection, token consume/cancel, and delay.
- docs: document Reserver across doc.go, limiter.go, README (core concepts,
  Allow/Wait/Reserve, features), docs/TOKEN_BUCKET.md, docs/CUSTOM_STORAGE.md
  (a Valkey Reserver impl), and docs/MIGRATION.md (the *rate.Limiter ->
  RateLimiter return-type change and how to adapt).

BREAKING: code type-asserting manager.GetOrAdd(key) to *rate.Limiter must assert
ratelimiter.Reserver (preferred) or ratelimiter.RateLimiter instead. Allow/Wait/
Burst usage is unchanged. go build/vet/test -race all pass; examples run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@christiangda christiangda changed the title docs: custom Storage example + Redis/Valkey distributed guide feat: Reserver interface + custom Storage example & Redis/Valkey guide Jul 11, 2026
@christiangda christiangda self-assigned this Jul 11, 2026
@christiangda
christiangda merged commit 3513d1b into main Jul 11, 2026
5 checks passed
@christiangda
christiangda deleted the docs/custom-storage-guide branch July 11, 2026 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant