feat(launcher): initial stable release v0.9.0
Application lifecycle manager enforcing a three-phase init/wire/start sequence with reverse-order graceful shutdown and per-component stop timeouts. What's included: - `Component` interface (OnInit / OnStart / OnStop) and `Hook` type for BeforeStart wiring functions - `Launcher` interface with Append, BeforeStart, Run (blocks on SIGINT/SIGTERM), and idempotent Shutdown(ctx) - `New(logger, opts...)` constructor with configurable ComponentStopTimeout (default 15 s); no global state Tested-via: todo-api POC integration Reviewed-against: docs/adr/
This commit is contained in:
43
docs/adr/ADR-003-before-start-hooks.md
Normal file
43
docs/adr/ADR-003-before-start-hooks.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# ADR-003: BeforeStart Hooks for Dependency Injection Wiring
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03-18
|
||||
|
||||
## Context
|
||||
|
||||
After all components have been initialised (`OnInit`), some wiring cannot be
|
||||
expressed at construction time because it requires the initialised state of multiple
|
||||
components simultaneously. For example, an HTTP server may need to register routes
|
||||
that reference handler functions which themselves hold references to a database
|
||||
client — but the database client only becomes ready after `OnInit` runs, which is
|
||||
after all components are constructed.
|
||||
|
||||
One alternative is to wire everything in `main` before calling `Run`, but that
|
||||
requires `main` to know the internal structure of every component, defeating
|
||||
encapsulation. Another alternative is to wire in `OnStart`, but at that point other
|
||||
components may already be running and the window for setup errors is narrower.
|
||||
|
||||
## Decision
|
||||
|
||||
`Launcher.BeforeStart(hooks ...Hook)` registers functions of type `func() error`
|
||||
that are called after all `OnInit` calls succeed and before any `OnStart` call
|
||||
begins. Hooks are called in registration order. If any hook returns an error, `Run`
|
||||
returns that error immediately without proceeding to `OnStart`.
|
||||
|
||||
`Hook` is a plain function type with no parameters beyond what closures capture.
|
||||
This allows hooks to close over the components they wire, without `launcher` needing
|
||||
to know anything about those components beyond the `Component` interface.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Dependency injection wiring is expressed as closures registered with
|
||||
`BeforeStart`, keeping `main` as the composition root without exposing internals.
|
||||
- All `OnInit` guarantees (connections open, ports bound, resources allocated) are
|
||||
satisfied before any hook runs — hooks can safely call methods on initialised
|
||||
components.
|
||||
- Hook errors abort the lifecycle cleanly. No components have started yet, so no
|
||||
cleanup of running services is needed (though `OnStop` will still run for any
|
||||
components that successfully ran `OnInit`, consistent with ADR-001's behaviour
|
||||
when `OnStart` fails).
|
||||
- The pattern scales to any number of wiring steps without adding methods to the
|
||||
`Component` interface.
|
||||
Reference in New Issue
Block a user