CLI & Worker Lifecycle Architecture
To ensure a "No-Magic" and deterministic experience across the modular monolith, Framework M follows a strictly separated two-phase lifecycle for all entry points (Web, CLI, Workers, REPL).
The Two-Phase Lifecycle
The framework distinguishes between wiring the application and activating its connections.
1. Hydration Phase (Synchronous)
Goal: Build the "brain" of the application.
- Action: Call
hydrate_apps(container). - Behavior: Scans entry points, loads provider overrides, discovers background jobs, and runs all
BootstrapProtocolsteps. - Context: Mandatory for ALL entry points.
- Side Effects: Metadata decoration, DI wiring, job registration, and configuration loading. No network connections are opened.
- Job Discovery: During this phase, the framework scans the
framework_m.jobsentry point group inpyproject.tomland imports the specified modules, triggering@jobdecorators to populate the globalJobRegistry.
2. Activation Phase (Asynchronous)
Goal: Open the "pipes" (connections) for long-lived processing.
- Action: Call
await container.startup_hooks(). - Behavior: Executes all async functions registered in the container's startup hook list.
- Context: Required for Web Apps and Background Workers.
- Side Effects: Connecting to NATS, initializing DB connection pools, starting socket listeners.
Contract for Entry Points
Web Application
The web application (create_app) handles both phases automatically:
- Hydration: Happens during the factory call.
- Activation: Happens inside the Litestar
app_lifespangenerator.
Background Workers
Workers (e.g., m worker) must manually orchestrate both phases to ensure services like NATS and DB are ready:
# Phase 1: Hydration
container = Container()
hydrate_apps(container)
async def start_worker():
# Phase 2: Activation
await container.startup_hooks()
try:
await run_processing_loop()
finally:
# Phase 3: Teardown
await container.shutdown_hooks()
CLI Commands (Lightweight vs. Full)
Standard CLI commands (like m info or m migrate) are "Lightweight" by default.
- One-Shot Commands: Only perform Phase 1 (Hydration). If they need a database, they use the
engineprovided by the hydration phase, which opens a connection on-demand and closes it when the command ends. - Long-Lived CLI Commands: Commands that act like mini-servers (e.g., an interactive shell or a listener) should perform Phase 2 (Activation).
Why this matters?
- Speed: CLI commands stay fast because they don't connect to NATS or warm up heavy pools unless they specifically ask for it.
- Determinism: The "Wiring" is always complete before any "Execution" begins.
- Isolation: Prevents side effects (like NATS subscriptions) from happening when you just want to check a configuration or run a migration.