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

30
CHANGELOG.md Normal file
View File

@@ -0,0 +1,30 @@
# Changelog
All notable changes to this module will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.9.0] - 2026-03-18
### Added
- `Component` interface — `OnInit() error` (open connections, allocate resources), `OnStart() error` (start background goroutines and listeners), `OnStop() error` (graceful shutdown and resource release)
- `Hook` type — `func() error` used to register dependency-injection wiring functions that run between `OnInit` and `OnStart`
- `Options` struct — `ComponentStopTimeout time.Duration` (maximum time allowed for each component's `OnStop`; default 15 seconds); zero value is valid
- `Launcher` interface — `Append(components ...Component)`, `BeforeStart(hooks ...Hook)`, `Run() error`, `Shutdown(ctx context.Context) error`
- `New(logger logz.Logger, opts ...Options) Launcher` — constructs a `Launcher`; no package-level singletons or global state; variadic opts apply the first element if provided
- `Launcher.Append` — registers one or more components in order; startup proceeds in registration order, shutdown in reverse
- `Launcher.BeforeStart` — registers hooks that run after all `OnInit` calls complete and before any `OnStart` call; correct place for dependency injection wiring
- `Launcher.Run() error` — executes the full lifecycle (OnInit → BeforeStart hooks → OnStart → wait → reverse-order OnStop); blocks until `SIGINT`, `SIGTERM`, or `Shutdown` is called
- `Launcher.Shutdown(ctx context.Context) error` — triggers a graceful shutdown programmatically and waits for `Run` to return; idempotent via `sync.Once`, safe to call from multiple goroutines; ctx controls the caller-side wait timeout only
- OS signal handling for `SIGINT` and `SIGTERM` built into `Run` with automatic `signal.Stop` cleanup on return
- Per-component independent stop timeout: each component's `OnStop` runs in its own goroutine and is abandoned (with an error log) if it exceeds `ComponentStopTimeout`
### Design Notes
- The three-phase lifecycle (OnInit / BeforeStart / OnStart) cleanly separates resource allocation from dependency wiring from service activation, ensuring no component begins serving traffic before all its dependencies are fully initialized.
- Shutdown runs in strict reverse registration order with a per-component independent timeout, so a stalled component cannot block others from stopping; worst-case shutdown time is `n × ComponentStopTimeout`.
- `Shutdown` closes a channel via `sync.Once` rather than using a mutex or flag, making it genuinely safe to call concurrently from an OS signal handler and a test teardown racing against each other.
[0.9.0]: https://code.nochebuena.dev/go/launcher/releases/tag/v0.9.0