Skip to content

๐Ÿ”„ DI Container Migration Guide (TD-009)

This guide helps you migrate from the deprecated DI implementations to the new unified Container system introduced in TD-009.

๐Ÿ“‹ Overview

TD-009 consolidates the previous dual-container system into a single, enhanced Container that provides:

  • Lifecycle Management: Singleton vs transient service lifecycles
  • Type Safety: Reflection-based validation and generic type support
  • Rich API: Comprehensive registration and resolution methods
  • Better Performance: Provider-based lazy loading with caching
  • Future-Ready: Foundation for automatic dependency injection (Phase 3)

๐Ÿšจ Deprecated Components

The following components are deprecated and will be removed in a future version:

Component Status Replacement
Container (simple) โŒ Deprecated Container (new)
DiContext โŒ Deprecated Container (new)
DiContextBuilder โŒ Deprecated ContainerBuilder
DiContextItem โŒ Deprecated ContainerItem

๐Ÿ”„ Migration Patterns

1. Simple Container Migration

Before (Deprecated):

from loryth.common.di import SimpleContainer  # Will show deprecation warning

container = SimpleContainer()
container.register(IUserService, user_service_instance)
service = container.resolve(IUserService)

After (New):

from loryth.common.di import Container

container = Container()
container.register_instance(IUserService, user_service_instance)
service = container.resolve(IUserService)

2. DiContext Migration

Before (Deprecated):

from loryth.common.di import DiContext, DiContextBuilder

builder = DiContextBuilder()
builder.service(UserService)
builder.config(app_config)
context = builder.build()
service = context.resolve(UserService)

After (New):

from loryth.common.di import Container, ContainerBuilder

# Option 1: Direct container usage
container = Container()
container.register_singleton(UserService, UserService)
container.register_instance(AppConfig, app_config)
service = container.resolve(UserService)

# Option 2: Builder pattern (with automatic dependency injection)
builder = ContainerBuilder()
builder.service(UserService)  # Auto-registers dependencies
builder.config(app_config)
container = builder.build()
service = container.resolve(UserService)

3. Factory Registration Migration

Before (DiContext):

builder = DiContextBuilder()
builder.factory(IEmailService, lambda ctx: SmtpEmailService(ctx.resolve(ILogger)))
context = builder.build()

After (New):

container = Container()
container.register_factory(IEmailService, lambda c: SmtpEmailService(c.resolve(ILogger)))

# Or using builder
builder = ContainerBuilder()
builder.factory(IEmailService, lambda c: SmtpEmailService(c.resolve(ILogger)))
container = builder.build()

๐Ÿ†• New Features

Lifecycle Management

The new Container supports explicit lifecycle control:

container = Container()

# Singleton: same instance every time
container.register_singleton(IUserService, UserService)

# Transient: new instance every time
container.register_transient(IEmailService, EmailService)

# Instance: pre-created instance
container.register_instance(AppConfig, config)

# Factory: custom creation logic
container.register_factory(ILogger, lambda c: create_logger(c.resolve(AppConfig)))

Enhanced Resolution

container = Container()

# Basic resolution (throws if not found)
service = container.resolve(IUserService)

# Safe resolution (returns None if not found)
service = container.try_resolve(IUserService)

# Multiple implementations
services = container.resolve_all(IPlugin)

# Check registration
if container.has_service(IUserService):
    service = container.resolve(IUserService)

Type Safety

The new container provides runtime type validation:

container = Container()

# This will raise DiRegistrationError if types are incompatible
container.register_singleton(IUserService, EmailService)  # Type mismatch!

# This will raise DiResolutionError if provider returns wrong type
def bad_factory(c: Container) -> IUserService:
    return EmailService()  # Wrong type!

container.register_factory(IUserService, bad_factory)
container.resolve(IUserService)  # Raises DiResolutionError

๐Ÿ“– Complete Migration Examples

Example 1: Simple Service Registration

Before:

from loryth.common.di import Container

# Old simple container
container = Container()
container.register(ILogger, ConsoleLogger())
container.register(IUserService, UserService())

logger = container.resolve(ILogger)
user_service = container.resolve(IUserService)

After:

from loryth.common.di import Container

container = Container()
container.register_instance(ILogger, ConsoleLogger())
container.register_instance(IUserService, UserService())

logger = container.resolve(ILogger)
user_service = container.resolve(IUserService)

Example 2: Complex Service Hierarchy

Before:

from loryth.common.di import DiContextBuilder

builder = DiContextBuilder()
builder.service(UserService)      # Auto-registers dependencies
builder.service(EmailService)
builder.config(app_config)
context = builder.build()

user_service = context.resolve(UserService)

After:

from loryth.common.di import ContainerBuilder

# Option 1: Using builder (similar to old DiContextBuilder)
builder = ContainerBuilder()
builder.singleton(IUserRepository, SqlUserRepository)  # Interface mapping
builder.singleton(IEmailService, SmtpEmailService)     # Interface mapping
builder.service(UserService)      # Auto-registers with dependency injection
builder.config(app_config)
container = builder.build()

user_service = container.resolve(UserService)

# Option 2: Direct container usage with explicit factories
container = Container()
container.register_singleton(ILogger, ConsoleLogger)
container.register_factory(IUserRepository, lambda c: SqlUserRepository(c.resolve(ILogger)))
container.register_factory(IEmailService, lambda c: SmtpEmailService(c.resolve(ILogger)))
container.register_factory(UserService, lambda c: UserService(
    c.resolve(IUserRepository),
    c.resolve(IEmailService)
))
container.register_instance(AppConfig, app_config)

user_service = container.resolve(UserService)

Example 3: Multiple Implementations

Before:

builder = DiContextBuilder()
builder.factory(IPlugin, lambda ctx: SecurityPlugin())
builder.factory(IPlugin, lambda ctx: LoggingPlugin())
context = builder.build()

plugins = context.resolve_all(IPlugin)  # Not available in old DiContext

After:

container = Container()
container.register_factory(IPlugin, lambda c: SecurityPlugin())
container.register_factory(IPlugin, lambda c: LoggingPlugin())

plugins = container.resolve_all(IPlugin)  # Returns all registered implementations

โš ๏ธ Breaking Changes

1. Import Changes

  • Default Container import now refers to the new implementation
  • Old simple Container available as SimpleContainer (deprecated)
  • DiContext and DiContextBuilder show deprecation warnings

2. API Changes

  • register() โ†’ register_instance(), register_singleton(), or register_transient()
  • Factory registration now uses register_factory() instead of builder pattern
  • Resolution methods remain compatible but with enhanced type safety

3. Behavior Changes

  • Type validation is now performed during registration and resolution
  • Lifecycle is explicit rather than implicit
  • Caching behavior may differ for complex dependency graphs

๐ŸŽฏ Migration Checklist

  • Update imports: Change to new Container import
  • Replace registration calls:
  • register() โ†’ register_instance()
  • Add lifecycle specification (register_singleton, register_transient)
  • Update builders: DiContextBuilder โ†’ ContainerBuilder
  • Test functionality: Ensure services resolve correctly
  • Handle deprecation warnings: Update deprecated API usage
  • Review error handling: Adapt to new exception types

๐Ÿงช Testing Your Migration

def test_migration_compatibility():
    """Test that migrated container works correctly."""
    container = Container()

    # Register services
    container.register_singleton(ILogger, ConsoleLogger)
    container.register_factory(IUserService, lambda c: UserService(c.resolve(ILogger)))

    # Test resolution
    logger = container.resolve(ILogger)
    user_service = container.resolve(IUserService)

    # Test lifecycle behavior
    logger2 = container.resolve(ILogger)
    assert logger is logger2  # Singleton behavior

    user_service2 = container.resolve(IUserService)
    assert user_service is not user_service2  # Factory behavior (transient)

    # Test type safety
    assert isinstance(logger, ConsoleLogger)
    assert isinstance(user_service, UserService)

๐Ÿ†˜ Migration Support

If you encounter issues during migration:

  1. Check deprecation warnings: They provide specific guidance for each deprecated component
  2. Review error messages: New type validation provides detailed error information
  3. Use ContainerBuilder: For complex scenarios, the builder provides automatic dependency injection
  4. Test incrementally: Migrate one service at a time to isolate issues

๐Ÿš€ Future Phases

The new Container is designed to support upcoming DI roadmap phases:

  • Phase 2: Bootstrapping and cycle detection
  • Phase 3: Automatic dependency injection
  • Phase 4: Configuration binding
  • Phase 5: Module scanning and auto-registration

Your migration to the new Container prepares your code for these future enhancements.