# 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`.