Skip to content

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:

  1. Registration Phase: Executes registration steps on the container
  2. Validation Phase: Validates dependency graph for cycles
  3. 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 services
  • TransientRegistrationStep: Register transient services
  • FactoryRegistrationStep: Register factory functions
  • InstanceRegistrationStep: 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

See Also