Bootstrap Runtime Guide¶
Overview¶
The BootstrapRuntime provides external orchestration for DI container bootstrap operations, separating bootstrap coordination from the container itself. This enables deterministic, idempotent bootstrap workflows with comprehensive observability.
Quick Start¶
from loryth.common.di.bootstrap import (
BootstrapRuntime, BootstrapPlan,
SingletonRegistrationStep, TransientRegistrationStep
)
from loryth.common.di.container import Container
# Create container and plan
container = Container()
plan = BootstrapPlan(
registrations=(
SingletonRegistrationStep(ICacheWarmup, CacheWarmup),
TransientRegistrationStep(ILogger, ConsoleLogger),
),
validate_dependencies=True,
eager_prepare=True,
)
# Execute bootstrap
runtime = BootstrapRuntime()
runtime.bootstrap(container, plan)
# Services are ready to use
service = container.resolve(ICacheWarmup)
logger = container.resolve(ILogger)
Core Concepts¶
BootstrapRuntime¶
The orchestrator that coordinates the three-phase bootstrap process:
- Registration Phase: Executes registration steps on the container
- Validation Phase: Validates dependency graph for cycles
- Preparation Phase: Eagerly resolves singleton services
BootstrapPlan¶
Immutable specification defining what to bootstrap:
- Registration Steps: Type-safe operations to register services
- Validation Flag: Whether to check for dependency cycles
- Preparation Flag: Whether to eagerly resolve singletons
Registration Steps¶
Type-safe wrappers around container registration operations:
SingletonRegistrationStep: Register singleton servicesTransientRegistrationStep: Register transient servicesFactoryRegistrationStep: Register factory functionsInstanceRegistrationStep: Register pre-created instances
Basic Usage Patterns¶
Pattern 1: Manual Registration with Bootstrap¶
# Register services directly
container = Container()
container.register_singleton(IUserService, UserService)
container.register_transient(IEmailService, EmailService)
# Bootstrap with validation only
plan = BootstrapPlan(
registrations=(), # Empty - services already registered
validate_dependencies=True,
eager_prepare=True,
)
runtime = BootstrapRuntime()
runtime.bootstrap(container, plan)
Pattern 2: Plan-Based Registration¶
# Build complete plan with all registrations
plan = BootstrapPlan(
registrations=(
SingletonRegistrationStep(IUserService, UserService),
TransientRegistrationStep(IEmailService, EmailService),
FactoryRegistrationStep(
IDatabase,
lambda c: DatabaseConnection(c.resolve(IConfig))
),
InstanceRegistrationStep(IConfig, load_config()),
),
validate_dependencies=True,
eager_prepare=True,
)
container = Container()
runtime = BootstrapRuntime()
runtime.bootstrap(container, plan)
Pattern 3: Testing with Minimal Validation¶
def test_service_with_mocked_dependencies():
"""Test service without full dependency validation."""
container = Container()
plan = BootstrapPlan(
registrations=(
SingletonRegistrationStep(IService, Service),
# Service depends on ILogger, but we're mocking it
),
validate_dependencies=False, # Skip cycle detection
eager_prepare=False, # Don't resolve eagerly
)
runtime = BootstrapRuntime()
runtime.bootstrap(container, plan)
# Container is ready but will fail on resolve if dependencies missing
# This is useful for unit tests that mock dependencies
Advanced Usage¶
Idempotent Re-Bootstrap¶
# Initial bootstrap
container = Container()
plan1 = BootstrapPlan(
registrations=(
SingletonRegistrationStep(IServiceA, ServiceA),
),
)
runtime = BootstrapRuntime()
runtime.bootstrap(container, plan1)
# Later: discover more services (e.g., from plugin scanning)
plan2 = BootstrapPlan(
registrations=(
SingletonRegistrationStep(IServiceA, ServiceA), # Duplicate - no-op
SingletonRegistrationStep(IServiceB, ServiceB), # New service
),
)
runtime.bootstrap(container, plan2) # Safe - idempotent
# Check metrics
stats = runtime.metrics.snapshot
print(f"Total bootstraps: {stats.bootstraps.total}") # 2
print(f"Total registration steps: {stats.registrations.total_steps_executed}") # 3 (2 from plan2, 1 skipped)
Factory Registration with Instance Injection¶
# Pre-load configuration
config = load_config_from_file("app.yaml")
# Build plan with mixed registration types
plan = BootstrapPlan(
registrations=(
InstanceRegistrationStep(IConfig, config), # Pre-created instance
FactoryRegistrationStep(
IDatabase,
lambda c: DatabaseConnection(c.resolve(IConfig).db_url),
),
SingletonRegistrationStep(IUserService, UserService),
),
)
runtime = BootstrapRuntime()
runtime.bootstrap(container, plan)
# All services resolved with config injected
user_service = container.resolve(IUserService)
Observability¶
Container Metrics¶
Track operational statistics for a specific container:
container = Container()
container.register_singleton(IService, Service)
container.resolve(IService)
stats = container.metrics.snapshot
print(f"Cache hit rate: {stats.resolution.cache_hit_rate:.2%}")
print(f"Total registrations: {stats.registration.count}")
print(
f"Resolutions: {stats.resolution.count - stats.resolution.failure_count} "
f"successes, {stats.resolution.failure_count} failures"
)
Bootstrap Metrics¶
Track cumulative bootstrap execution across all containers:
runtime = BootstrapRuntime()
runtime.bootstrap(container1, plan)
runtime.bootstrap(container2, plan)
stats = runtime.metrics.snapshot
print(f"Total bootstraps: {stats.bootstraps.total}")
print(f"Success rate: {stats.bootstraps.success_rate:.2%}")
print(f"Average duration: {stats.bootstraps.average_duration_ms:.2f}ms")
print(
f"Phase breakdown: registration={stats.timings.phase_breakdown_ms.registration:.2f}ms, "
f"validation={stats.timings.phase_breakdown_ms.validation:.2f}ms, "
f"preparation={stats.timings.phase_breakdown_ms.preparation:.2f}ms"
)
Integration with Application Bootstrap¶
from loryth.common.di.bootstrap import BootstrapPlan, BootstrapRuntime
class ApplicationBootstrap:
def __init__(self, scanner: DiPackageScanner):
self._scanner = scanner
self._runtime = BootstrapRuntime()
def run(self, app_cls: type) -> Container:
# Scan for annotated services
scanned_classes = self._scanner.scan(ScanningConfiguration(...))
# Build registration plan from scanned classes
steps = []
for cls in scanned_classes:
annotation = find_service_annotation(cls)
if annotation.lifecycle == ServiceLifecycle.SINGLETON:
steps.append(SingletonRegistrationStep(cls, cls))
elif annotation.lifecycle == ServiceLifecycle.TRANSIENT:
steps.append(TransientRegistrationStep(cls, cls))
# Create container and bootstrap
container = Container()
plan = BootstrapPlan(registrations=tuple(steps))
self._runtime.bootstrap(container, plan)
return container
Best Practices¶
1. Use Explicit Bootstrap¶
Always call BootstrapRuntime.bootstrap() explicitly rather than relying on implicit bootstrap:
# ✅ Good: Explicit bootstrap
container = Container()
plan = BootstrapPlan(registrations=(...))
runtime.bootstrap(container, plan)
# ❌ Avoid: Implicit bootstrap (no longer supported)
container = Container()
container.resolve(IService) # Will fail - container not bootstrapped
2. Leverage Idempotency¶
Take advantage of idempotent registrations for plugin systems and dynamic discovery:
# Safe to call multiple times with same services
for plugin_module in discovered_plugins:
plan = build_plan_from_module(plugin_module)
runtime.bootstrap(container, plan) # Duplicates are no-ops
3. Choose Appropriate Validation Level¶
# Production: Full validation
plan = BootstrapPlan(validate_dependencies=True, eager_prepare=True)
# Testing: Minimal validation
plan = BootstrapPlan(validate_dependencies=False, eager_prepare=False)
# Development: Validation only
plan = BootstrapPlan(validate_dependencies=True, eager_prepare=False)
4. Monitor Bootstrap Performance¶
runtime = BootstrapRuntime()
# ... multiple bootstrap calls ...
stats = runtime.metrics.snapshot
if stats.timings.phase_breakdown_ms.validation > 1000:
print("Warning: Dependency validation is slow - consider optimizing")
Error Handling¶
Bootstrap Failures¶
try:
runtime.bootstrap(container, plan)
except BootstrapError as e:
print(f"Bootstrap failed: {e}")
# Handle dependency cycle or validation error
except DiResolutionError as e:
print(f"Preparation failed: {e}")
# Handle singleton resolution failure during prepare()
Registration Conflicts¶
try:
plan = BootstrapPlan(
registrations=(
SingletonRegistrationStep(IService, ServiceA),
SingletonRegistrationStep(IService, ServiceB), # Conflict!
)
)
runtime.bootstrap(container, plan)
except DiRegistrationError as e:
print(f"Registration conflict: {e}")
# Handle ambiguous service registration