- Docs
- Concepts
- Container
Container
How the container resolves services lazily, retries on error, shares Job and Scheduler queues, and closes in order.
The container is the only object that knows how adapters are constructed, cached, retried, and closed. One container.New(cfg) per process gives lazy first use and dependency-ordered close.
The mental model
Section titled “The mental model”New(cfg) -> accessor() -> registry lookup -> cached instanceone per lazy first adapter by name retry on errorprocess use only from config clear on failureNo globals and no init() wiring. New(nil) is valid; Close(ctx) never opens anything. Config picks the adapter (<SERVICE>_ADAPTER or adapter:); the remaining keys feed typed Options (Config reference). Display config with config.RedactedServices, never raw option maps.
Resolve services
Section titled “Resolve services”Register the adapters you use (dbsqlite.Register()), build the container with container.New(config.Default()), then call accessors. Each returns one cached instance and retries after a failure.
DB(): plusTransactor()Cache(),Queue(): leaves, closed after dependents and snapshotsJob(): dispatcher over resolvedQueue, no registryScheduler(): sharesQueueviaJob(), closed firstEventBus():Eventbus()aliasRateLimit():Ratelimit()aliasGRPC(opts...): lazy*grpc.Server, no registryGRPCClient(target, opts...): gRPC client connectionRouter():fiberorstdhttpdriverLog(): outermost middlewareAI(): API key required, no keyless defaultAgent(),RAG(): overAI(andVectorStoreforRAG)
The rest share the service name:
Analytics() Auth() Billing() Crypto() Document() Flag() Geo() I18n()Idempotency() Lock() Mailer() Media() Notification() Observability()Outbox() Password() Payment() Permission() Resilience() Search()Secrets() Session() Storage() Tenant() VectorStore() Webhook() Workflow()Share one queue across jobs and the scheduler
Section titled “Share one queue across jobs and the scheduler”Job() builds a dispatcher over the already-resolved queue. Scheduler() injects that dispatcher, so no redundant connections open. Resolving Scheduler() resolves Job() and Queue() as a side effect.
Close services in order
Section titled “Close services in order”- Dependents (
scheduler,job,agent,rag; they hold backend refs): first. - Snapshots (everything without explicit ordering): middle.
- Leaves (
cache,queue): next. - Shared pools (
dband registry pools, after every borrower), then plugin services. - Independent (
grpcServerviaGracefulStop, forcedStopwhen ctx ends; gRPC client connections): last.
The container probes each service’s shutdown shape in order: Close(context.Context) error, Close() error, Stop() error, Shutdown(context.Context) error, then no-op (for example *job.Dispatcher).
Close(ctx) error: pooled or blocking services (db,cache,storage,lock).Close() error: lightweight or in-memory adapters (auth,queue,session,payment).Stop() error: scheduler cron only.Shutdown(ctx) error: observability providers (flush spans and metrics).- none: stateless adapters (
crypto,log,password,permission,router).
Design decisions and trade-offs
Section titled “Design decisions and trade-offs”A 5s per-service bound applies without a parent deadline; shared instances close once (pointer-identity dedup), failures join with errors.Join, and error values name the service only.
Troubleshooting
Section titled “Troubleshooting”An accessor errors on the first call only
A transient failure cleared the cache. Retry; investigate only if it repeats.
Close reports a timeout for one service
That adapter exceeded its bound. Give the parent a deadline or fix its Close.
errors.As for ClosePanicError never fires
Both close error types are values. Declare var pe container.ClosePanicError and pass &pe.
A service seems to close twice
A shared instance reached through two paths. Dedup closes it once.
The leak checker fails after adding a service
An adapter goroutine outlives Close (go test -tags=leak ./container/). Stop it in Close.