Rene Nochebuena 647810e4f7 docs(launcher): correct tier from 5 to 2
launcher only imports logz (Tier 1) — it belongs at Tier 2, not 5.
The wrong tier implied it had to be pushed last, when in reality worker,
postgres, mysql, sqlite, valkey, firebase, and httpserver all depend on it
and cannot be tagged before it.
2026-03-19 06:55:34 -06:00

launcher

Application lifecycle manager for Go services.

Module code.nochebuena.dev/go/launcher
Tier 2 — depends on logz
Go 1.25
Dependencies code.nochebuena.dev/go/logz

Overview

launcher orchestrates a repeatable lifecycle sequence — init, assemble, start, wait, shutdown — across any number of registered infrastructure components. It does not manage dependency injection, provide a service locator, or define what your components are; it only drives them through a consistent set of phases.

Installation

go get code.nochebuena.dev/go/launcher

Quick start

logger := logz.New(logz.Options{})
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)
}

Usage

Implementing Component

Any infrastructure piece implements the three-method interface:

type Component interface {
    OnInit()  error  // open connections, allocate resources
    OnStart() error  // start goroutines, begin serving
    OnStop()  error  // stop and release all resources
}

Lifecycle sequence

OnInit  (all components, in registration order)
BeforeStart hooks (in registration order)
OnStart (all components, in registration order)
── application is running ──
OnStop  (all components, in reverse registration order)

Run blocks at the "application is running" stage until SIGINT/SIGTERM is received or Shutdown is called. If any OnInit, hook, or OnStart returns an error, Run returns that error immediately (triggering stopAll for OnStart failures).

BeforeStart hooks

Hooks run after all OnInit calls and before any OnStart call. Use them to wire dependencies that require all components to be initialized first:

lc.BeforeStart(func() error {
    // db and cache are both initialized at this point
    return server.RegisterRoutes(db, cache)
})

Programmatic shutdown

Shutdown triggers graceful shutdown and waits for Run to return. Use it in tests or when your application needs to shut down without an OS signal:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := lc.Shutdown(ctx); err != nil {
    // ctx expired before Run returned
}

Shutdown is idempotent — safe to call multiple times.

Customising the stop timeout

lc := launcher.New(logger, launcher.Options{
    ComponentStopTimeout: 5 * time.Second,
})

The default is 15 seconds per component. The timeout controls how long stopAll waits for each individual OnStop call; it is independent of the ctx passed to Shutdown.

Design decisions

  • Run() returns error instead of calling os.Exit. The original implementation called os.Exit(1) directly, making the package untestable. Returning the error lets the caller (typically main) decide what to do.
  • shutdownCh + doneCh channel pair. shutdownCh is closed by Shutdown to unblock Run's wait loop. doneCh is closed by Run (via defer) before it returns. This guarantees that Shutdown unblocks even when Run returns early due to an error in OnInit or a hook.
  • Components stopped in reverse registration order. This mirrors the LIFO teardown convention: the last component registered (typically the outermost layer, e.g. HTTP server) is stopped first, allowing inner layers (e.g. database) to remain available until no longer needed.
  • Per-component timeout, not a global one. Each OnStop gets its own independent timeout window. A single slow component does not consume the budget for all others.

Ecosystem

Tier 0   xerrors  rbac
Tier 1   logz  valid
Tier 2 ▶ launcher
Tier 3   postgres  mysql  sqlite  valkey  firebase  httpclient  worker
Tier 4   httputil  httpmw  httpauth-firebase  httpserver
Tier 5   telemetry

launcher is the lifecycle backbone of the framework. Every Tier 3+ module implements launcher.Component; Run() drives them all through init → start → stop.

License

MIT

Description
Three-phase application lifecycle manager with graceful shutdown and signal handling.
Readme MIT 48 KiB
2026-03-18 17:50:05 -06:00
Languages
Go 100%