Documentation
¶
Overview ¶
Package app holds the application assembly layer of the SDK. It provides the reusable pieces of the "build & run" pattern — a deps/provider/server assembly layout — so an application does not re-implement them.
The pattern: a Deps container of lazy dim.Provider fields, an ordered slice of Initializers (infrastructure -> core -> handlers -> workers), and InitDeps which runs each initializer and registers its cleanup with the global closer for LIFO shutdown. Deps itself is application-specific; the SDK supplies the generic InitDeps over it.
Usage ¶
// deps.go (in your application)
var Initializers = []app.Initializer[Deps]{
initTracer, initStore, initBroker, // infrastructure
initWidgetCore, // core
initWidgetHandler, // handlers
}
// serve command
d := &Deps{Opts: cfg, Logger: log}
if err := app.InitDeps(d, Initializers); err != nil {
return err
}
defer func() { _ = closer.CloseSync() }() // LIFO release of every cleanup
Each initializer typically assigns a dim.Provider built from a provider factory (value, cleanup, error) and returns that cleanup, so deps declares WHAT to build and provider declares HOW.
Commands and partial dependency sets ¶
The runnable is not always a server (or group of servers): a CLI command — e.g. connect to the DB, run a business function, publish to a queue — also needs dependencies, sometimes only a subset. Two facts make this easy:
dim providers are LAZY: an initializer only wires a provider and registers its (no-op-until-built) cleanup; the resource is constructed on first d.X(ctx). So you can run the FULL Initializers slice and only the dependencies the command actually touches get built (and cleaned up) — handlers, consumers and idle infra never connect.
Initializers is just data. A command that must NOT touch some infrastructure (or wants to guarantee only certain resources connect) passes a SUBSET slice instead. Compose subsets from named groups with the standard library:
var ( Infra = []app.Initializer[Deps]{initTracer, initStore, initQueue} Core = []app.Initializer[Deps]{initWidgetCore} Handlers = []app.Initializer[Deps]{initWidgetHandler, initWidgetGRPC} ) // serve: everything. _ = app.InitDeps(d, slices.Concat(Infra, Core, Handlers)) // a "drain queue" command: just store + queue + core, no handlers. _ = app.InitDeps(d, slices.Concat(Infra, Core))
API ¶
- Initializer[D]: func(*D) (dim.CleanupFunc, error) — builds one dependency.
- InitDeps[D]: run a []Initializer[D], registering cleanups with the global closer; stops at the first error.
- Run: supervise runnables (a server.Set) until a signal or failure, then graceful shutdown + closer release. The serve-command entry point.
- RunCommand[D]: bootstrap a one-shot CLI command — init a subset of deps, run fn with a signal-cancelable context, release resources after.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func InitDeps ¶
func InitDeps[D any](d *D, inits []Initializer[D]) error
InitDeps runs each initializer in order against d, registering every non-nil cleanup with the global closer so resources are released in LIFO order on shutdown (pair it with a deferred closer.CloseSync in your serve command). It stops at the first error and returns it; cleanups already registered still run when the caller closes.
This is the reusable form of the per-app InitDeps loop: declare a []app.Initializer[Deps] and call app.InitDeps(&deps, inits).
func Run ¶
Run supervises the given runnables until a shutdown signal arrives or one of them fails, then performs a graceful shutdown and releases every resource registered with the global closer (LIFO). It owns the signal context and the closer lifecycle, so a serve command shrinks to: build deps, build the server set, call Run.
It returns nil on a clean, signal-driven shutdown and the triggering error when a runnable fails on its own.
func RunCommand ¶
func RunCommand[D any](ctx context.Context, cfg CommandConfig, d *D, inits []Initializer[D], fn func(ctx context.Context, d *D) error) error
RunCommand bootstraps a one-shot command: it initializes the given (possibly partial) set of dependencies on d — registering their cleanups with the global closer — runs fn with a signal-cancelable context, then releases every resource via the closer (LIFO), even when fn fails.
This is the CLI counterpart of Run: a command (e.g. connect to the DB, run a business function, publish to a queue) assembles only the dependencies it needs by passing a subset of the application's initializers (compose named groups with slices.Concat), or the full set — dim is lazy, so only the dependencies fn actually touches are built.
Types ¶
type CommandConfig ¶
type CommandConfig struct {
// Signals override the cancellation signals (default SIGINT, SIGTERM); the
// context passed to fn is canceled when one arrives, so a long command can
// abort cleanly.
Signals []os.Signal
}
CommandConfig configures RunCommand.
type Initializer ¶
type Initializer[D any] func(*D) (dim.CleanupFunc, error)
Initializer builds and assigns one dependency onto the container D, returning an optional cleanup. It is the unit of the assembly pattern: a Deps container of lazy dim.Provider fields plus an ordered slice of Initializers. A typical initializer assigns a provider and returns the resource's cleanup:
func initStore(d *Deps) (dim.CleanupFunc, error) {
d.DB = dim.NewResource("DB", provider.Postgres(&d.Opts, d.Logger))
return nil, nil // resource cleanup is returned by NewResource via the provider
}
D is the application's own dependency container — the SDK stays agnostic of its shape.
type RunConfig ¶
type RunConfig struct {
// Logger is passed to the worker.Group for supervision logging (may be nil).
Logger *slog.Logger
// ShutdownTimeout bounds how long the group waits for runnables to Stop
// (0 means no bound).
ShutdownTimeout time.Duration
// Signals override the shutdown signals (default SIGINT, SIGTERM).
Signals []os.Signal
}
RunConfig configures the long-lived run loop.