- Docs
- Extend
- Choosing batteries and adapters
Choosing batteries and adapters
A decision guide for picking the adapter behind each battery, from zero-infrastructure defaults to production backends.
Every battery is one small interface with several adapters, and switching adapters is a config change, not a code change. So the question is never “which library do I commit to” but “which adapter fits this stage”. Start on the zero-infrastructure defaults, then change one adapter: line when the deployment shape demands it.
The default for every battery in config.Default() runs with no external service (memory, local, sqlite, log, noop, …), which is why tests and zever serve work on a fresh checkout. The full table is in the adapters matrix; this page covers the choices that matter.
The one rule
Section titled “The one rule”Pick by where state lives and who must see it:
- One process, throwaway state (tests, local dev, a single binary): the
memoryadapter. - Several processes must share the state (more than one server replica, or server plus worker): an adapter backed by a shared service,
redisor yourpostgresdatabase. - You want fewer moving parts: the
dbadapter that reuses the database you already run, instead of adding Redis.
Common choices
Section titled “Common choices”| Battery | Local and tests | Production when… | Notes |
|---|---|---|---|
db |
sqlite (pure Go, no cgo) |
postgres for anything multi-instance or write-heavy. |
sqlite :memory: is per connection; use a file path under t.TempDir() in tests. |
cache |
memory |
redis for a shared cache; db if you run no Redis. |
memory is per process, so replicas see different values. |
queue |
memory |
redis for separate worker tiers; db for a shared queue without Redis. |
Only redis and db let another host pop. See Queue. |
session |
memory |
redis or db so a session survives restarts and spans replicas. |
|
auth |
jwt |
jwt for stateless APIs; session for cookie apps; oidc to verify tokens from an identity provider. |
jwt needs a secret of at least 32 bytes. |
ratelimit |
memory |
redis when limits must hold across replicas. |
|
lock, idempotency |
memory |
redis (or db for idempotency) for cross-instance correctness. |
|
eventbus |
memory |
redis for fan-out across instances. |
No durability for offline handlers. |
outbox |
memory |
db for transactional delivery; cdc for postgres logical replication. |
Defaults to db. |
scheduler |
embedded |
postgres for durable leased slots on several hosts. |
|
observability |
stdout |
otlp to a collector. |
noop for tests. |
log |
slog or pretty |
slog or zerolog for JSON logs; noop in tests. |
|
storage |
local |
s3 or r2 for object storage. |
|
search, vectorstore |
db |
meilisearch, postgres, pgvector, or qdrant when scale or features demand it. |
Start on db. |
secrets |
env |
vault when you need rotation or an audited store. |
env cannot rotate without a restart. |
permission |
noop |
rbac or casbin when you enforce authorization. |
|
router |
stdhttp |
stdhttp or fiber. |
Pick by the middleware ecosystem you want. |
Decide in order
Section titled “Decide in order”- Do I run more than one process of this service? If no, stay on
memory/sqlite. If yes, move the shared batteries (session,ratelimit,lock,idempotency,cache,queue) toredisordb. - Do I already run Redis? If yes, use it for the shared batteries. If no, the
dbadapters avoid adding a service at the cost of polling and database load. - Does it carry money, auth, or compliance? Do not use
stub,noop, ormemoryadapters for it. Thestubpayment and billing adapters verify nothing and hold no funds. - Do I need to read the choice back later? Run
zever doctorafter changing config: it resolves every enabled battery and reportsOKorFAILper battery.
Wiring a choice
Section titled “Wiring a choice”Set it via env var (e.g., CACHE_ADAPTER=redis), and make sure the adapter is registered. zever add cache/redis adds the go.mod require, the Register() call, the wiring.go entry, and the .env.example stanza in one step:
zever add cache/rediszever doctorAn enabled battery whose adapter was never registered fails at startup with a did-you-mean hint instead of at first use. To trim a binary to only the adapters you ship, see zever build --adapters in the CLI reference.