๐ 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
Containerimport now refers to the new implementation - Old simple
Containeravailable asSimpleContainer(deprecated) DiContextandDiContextBuildershow deprecation warnings
2. API Changes¶
register()โregister_instance(),register_singleton(), orregister_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
Containerimport - 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)
๐ Related Documentation¶
๐ Migration Support¶
If you encounter issues during migration:
- Check deprecation warnings: They provide specific guidance for each deprecated component
- Review error messages: New type validation provides detailed error information
- Use ContainerBuilder: For complex scenarios, the builder provides automatic dependency injection
- 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.