Skip to content
Zever
Home

Choosing batteries and adapters

A decision guide for picking the adapter behind each battery, from zero-infrastructure defaults to production backends.

3 min readEdit on GitHub

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.

Pick by where state lives and who must see it:

  • One process, throwaway state (tests, local dev, a single binary): the memory adapter.
  • Several processes must share the state (more than one server replica, or server plus worker): an adapter backed by a shared service, redis or your postgres database.
  • You want fewer moving parts: the db adapter that reuses the database you already run, instead of adding Redis.
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.
  1. 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) to redis or db.
  2. Do I already run Redis? If yes, use it for the shared batteries. If no, the db adapters avoid adding a service at the cost of polling and database load.
  3. Does it carry money, auth, or compliance? Do not use stub, noop, or memory adapters for it. The stub payment and billing adapters verify nothing and hold no funds.
  4. Do I need to read the choice back later? Run zever doctor after changing config: it resolves every enabled battery and reports OK or FAIL per battery.

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:

Terminal window
zever add cache/redis
zever doctor

An 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.