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:
2026-03-18 23:49:12 +00:00
commit f2e3faa1d6
15 changed files with 1109 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
# ADR-001: Three-Phase Lifecycle
**Status:** Accepted
**Date:** 2026-03-18
## Context
Application bootstrap has distinct concerns that must happen in a specific order:
resource allocation (open database connections, bind ports), dependency wiring
(connect components to each other after all are ready), and service activation
(start accepting requests, launch background goroutines). Conflating these phases
leads to ordering bugs: a component trying to use a dependency that has not been
initialised yet, or a service starting to accept requests before the database
connection pool is ready.
## Decision
The `Launcher` orchestrates four sequential phases, implemented in `Run()`:
1. **OnInit** — called for all registered components in registration order. Opens
connections, allocates resources. No component is started yet.
2. **BeforeStart** — all `Hook` functions registered via `BeforeStart(hooks ...Hook)`
are called in registration order, after all `OnInit` calls have succeeded. This
is the dependency injection wiring phase: all components are initialised and can
be queried, but none are serving yet.
3. **OnStart** — called for all components in registration order. Starts goroutines,
begins serving. If any `OnStart` fails, `stopAll` is called immediately and `Run`
returns an error.
4. **Wait**`Run` blocks on either an OS signal (`SIGINT`, `SIGTERM`) or a
programmatic `Shutdown()` call.
5. **OnStop (shutdown)**`stopAll` is called, running `OnStop` for all components
in reverse registration order.
Each phase gate is explicit: if any step returns an error, the lifecycle halts and
the error is returned to the caller. The caller (typically `main`) decides whether
to call `os.Exit(1)`.
## Consequences
- `OnInit` can safely assume nothing is running yet — safe to block on slow
operations like initial DNS resolution or schema migration.
- `BeforeStart` hooks see a fully initialised set of components, making it the
correct place for wiring that requires cross-component knowledge.
- `OnStart` can safely assume all dependencies are wired and all peers are
initialised — it is safe to start serving or spawning goroutines.
- The three-phase split eliminates an entire class of "component not ready" race
conditions without requiring any synchronisation between components.
- The `Component` interface requires all three methods to be implemented. Components
with no meaningful action for a phase return `nil`.