Files
launcher/docs/adr/ADR-003-before-start-hooks.md
Rene Nochebuena f2e3faa1d6 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/
2026-03-18 23:49:12 +00:00

2.1 KiB

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.