# launcher Application lifecycle manager: init, wire, start, wait, and graceful shutdown. ## Purpose `launcher` orchestrates the startup and shutdown of all infrastructure components (database pools, HTTP servers, background workers) in a Go service. It enforces a strict phase order — `OnInit` → `BeforeStart` hooks → `OnStart` → wait for signal → `OnStop` in reverse — so that components are never started before their dependencies are ready and never stopped before the components that depend on them. ## Tier & Dependencies **Tier:** 5 (application bootstrap only) **Imports:** `context`, `os`, `os/signal`, `sync`, `syscall`, `time` (stdlib); `code.nochebuena.dev/go/logz` (micro-lib) **Must NOT import:** `xerrors`, `rbac`, or any domain/application module. `launcher` is the composition root; it should not depend on what it orchestrates. ## Key Design Decisions - Three-phase lifecycle (`OnInit` / `BeforeStart` / `OnStart`) with reverse-order shutdown. See `docs/adr/ADR-001-three-phase-lifecycle.md`. - Shutdown runs in reverse registration order; each component gets an independent per-component timeout (default 15 s). See `docs/adr/ADR-002-reverse-order-shutdown.md`. - `BeforeStart` hooks run after all inits, before all starts — the correct place for dependency injection wiring. See `docs/adr/ADR-003-before-start-hooks.md`. - No singletons: `New(logger, opts...)` returns a `Launcher` interface; there is no package-level instance, no `sync.Once`, no global state. - `Shutdown(ctx)` is idempotent — safe to call from multiple goroutines. ## Patterns **Basic wiring:** ```go logger := logz.New(logz.Options{JSON: true}) lc := launcher.New(logger) lc.Append(db, cache, server) lc.BeforeStart(func() error { return server.RegisterRoutes(db, cache) }) if err := lc.Run(); err != nil { logger.Error("launcher failed", err) os.Exit(1) } ``` **Custom component stop timeout:** ```go lc := launcher.New(logger, launcher.Options{ ComponentStopTimeout: 30 * time.Second, }) ``` **Implementing the Component interface:** ```go type DBClient struct { pool *sql.DB } func (d *DBClient) OnInit() error { d.pool, err = sql.Open(...); return err } func (d *DBClient) OnStart() error { return nil } // no goroutines to start func (d *DBClient) OnStop() error { return d.pool.Close() } ``` **Programmatic shutdown (e.g. from a test):** ```go go func() { lc.Run() }() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() lc.Shutdown(ctx) ``` **Registration order matters:** ```go // Register dependencies before dependents lc.Append(db) // stopped last lc.Append(cache) lc.Append(server) // stopped first (reverse order) ``` ## What to Avoid - Do not perform dependency injection wiring in `OnInit` or `OnStart`. Use `BeforeStart` hooks; `OnInit` has no guarantee that other components are ready, and `OnStart` is too late (components may already be serving). - Do not create a package-level `Launcher` variable. `New()` is the only constructor; call it from `main`. - Do not call `os.Exit` inside a `Component` method. Return errors; let `main` decide whether to exit. - Do not register components after calling `Run`. `Append` and `BeforeStart` are not safe to call concurrently with `Run`. - Do not rely on `ComponentStopTimeout` as a substitute for proper `OnStop` implementation. A timed-out stop means resources may leak. ## Testing Notes - `compliance_test.go` asserts at compile time that `New(logz.New(Options{}))` returns a value satisfying `launcher.Launcher`. - `launcher_test.go` covers the full lifecycle: successful run + programmatic shutdown, `OnInit` failure aborting before `OnStart`, `OnStart` failure triggering `stopAll`, `BeforeStart` hook failure, reverse-order shutdown verification, and per-component stop timeout. - Tests use a lightweight `mockComponent` struct implementing `Component` with controllable error injection and call-order recording. - Run with plain `go test` — no external dependencies.