Skip to content

Migration from Other DI Frameworks

This guide covers migration from various other dependency injection frameworks to InjectQ, including framework-specific patterns and best practices.

๐ŸŒŸ Framework Comparison Overview

Framework Container Type Injection Style Scopes Async Support Key Features
InjectQ Explicit Decorator/Manual Flexible Full Performance, Testing, Profiling
dependency-injector Explicit Manual Limited Partial Providers, Containers
punq Explicit Manual Basic No Lightweight, Simple
lagom Explicit Manual Basic No Service Locator
aiodine Explicit Decorator Limited Yes Async-first

๐Ÿ”„ Migration from dependency-injector

Container Setup

# โŒ dependency-injector
from dependency_injector import containers, providers

class Container(containers.DeclarativeContainer):
    config = providers.Configuration()

    database = providers.Singleton(
        DatabaseConnection,
        config.database.url
    )

    user_service = providers.Factory(
        UserService,
        db=database
    )

# โœ… InjectQ
from injectq import InjectQ, Module

class AppModule(Module):
    def configure(self):
        # Configuration
        config = Config()
        self.bind(Config, config).singleton()

        # Database
        self.bind(DatabaseConnection, lambda: DatabaseConnection(
            self.container.get(Config).database.url
        )).singleton()

        # Services
        self.bind(UserService, UserService)

container = InjectQ()
container.install(AppModule())

Provider Migration

# โŒ dependency-injector providers
from dependency_injector import providers

class Container(containers.DeclarativeContainer):
    # Singleton provider
    database = providers.Singleton(DatabaseConnection, host="localhost")

    # Factory provider
    user_service = providers.Factory(UserService, db=database)

    # Configuration provider
    config = providers.Configuration()

    # Callable provider
    api_client = providers.Callable(
        lambda: ApiClient(config.api.endpoint())
    )

# โœ… InjectQ equivalent
class AppModule(Module):
    def configure(self):
        # Singleton
        self.bind(DatabaseConnection, lambda: DatabaseConnection(host="localhost")).singleton()

        # Factory (transient by default)
        self.bind(UserService, UserService)

        # Configuration
        config = Config()
        self.bind(Config, config).singleton()

        # Callable/Factory
        self.bind(ApiClient, lambda: ApiClient(
            self.container.get(Config).api.endpoint
        ))

Dependency Resolution

# โŒ dependency-injector
container = Container()
user_service = container.user_service()  # Factory call
database = container.database()          # Singleton call

# โœ… InjectQ
container = InjectQ()
container.install(AppModule())
user_service = container.get(UserService)  # Consistent interface
database = container.get(DatabaseConnection)

๐Ÿ”ง Migration from punq

Basic Setup

# โŒ punq
import punq

container = punq.Container()

# Register singleton
container.register(DatabaseService, scope=punq.Scope.singleton)

# Register with factory
container.register(UserService, factory=lambda: UserService(
    container.resolve(DatabaseService)
))

# Register instance
config = Configuration()
container.register(Configuration, instance=config)

# โœ… InjectQ
container = InjectQ()

# Register singleton
container.bind(DatabaseService, DatabaseService).singleton()

# Register with dependencies (auto-resolved)
container.bind(UserService, UserService)

# Register instance
config = Configuration()
container.bind(Configuration, config).singleton()

Interface Registration

# โŒ punq
from abc import ABC, abstractmethod

class IUserRepository(ABC):
    @abstractmethod
    def find_user(self, user_id: str):
        pass

class SqlUserRepository(IUserRepository):
    def find_user(self, user_id: str):
        return {"id": user_id}

container = punq.Container()
container.register(IUserRepository, SqlUserRepository)

# โœ… InjectQ
class IUserRepository(ABC):
    @abstractmethod
    def find_user(self, user_id: str):
        pass

class SqlUserRepository(IUserRepository):
    def find_user(self, user_id: str):
        return {"id": user_id}

container = InjectQ()
container.bind(IUserRepository, SqlUserRepository)

๐ŸŒŠ Migration from lagom

Service Registration

# โŒ lagom
from lagom import Container

container = Container()

# Register service
container.register(DatabaseService)

# Register with dependencies
container.register(UserService, DatabaseService)

# Register singleton
container.register(CacheService, is_singleton=True)

# โœ… InjectQ
container = InjectQ()

# Register service
container.bind(DatabaseService, DatabaseService)

# Register with auto-resolved dependencies
container.bind(UserService, UserService)

# Register singleton
container.bind(CacheService, CacheService).singleton()

Dependency Resolution

# โŒ lagom
database_service = container.resolve(DatabaseService)
user_service = container.resolve(UserService)

# โœ… InjectQ
database_service = container.get(DatabaseService)
user_service = container.get(UserService)

โšก Migration from aiodine

Async Service Registration

# โŒ aiodine
from aiodine import Container

container = Container()

# Register async service
@container.register
class AsyncUserService:
    async def __init__(self, db: AsyncDatabase):
        self.db = db
        await self.initialize()

    async def initialize(self):
        await self.db.connect()

# โœ… InjectQ
from injectq import InjectQ, inject

container = InjectQ()

class AsyncUserService:
    @inject
    async def __init__(self, db: AsyncDatabase):
        self.db = db
        await self.initialize()

    async def initialize(self):
        await self.db.connect()

# Register async service
container.bind(AsyncUserService, AsyncUserService)

Async Resolution

# โŒ aiodine
async def main():
    service = await container.resolve(AsyncUserService)
    result = await service.process()

# โœ… InjectQ
async def main():
    service = await container.aget(AsyncUserService)
    result = await service.process()

    # Or use async context
    async with container.async_scope():
        scoped_service = container.get(AsyncUserService)
        result = await scoped_service.process()

๐ŸŽฏ Advanced Migration Patterns

Configuration Management

# Generic configuration pattern
class ConfigurationManager:
    """Manages configuration across different DI frameworks."""

    def __init__(self, environment: str = "development"):
        self.environment = environment
        self.config_data = self._load_config()

    def _load_config(self):
        """Load configuration based on environment."""
        if self.environment == "production":
            return {
                "database": {
                    "host": "prod-db.example.com",
                    "port": 5432,
                    "name": "production_db"
                },
                "api": {
                    "endpoint": "https://api.example.com",
                    "timeout": 30
                }
            }
        else:
            return {
                "database": {
                    "host": "localhost",
                    "port": 5433,
                    "name": "test_db"
                },
                "api": {
                    "endpoint": "http://localhost:8080",
                    "timeout": 5
                }
            }

    def setup_injectq_container(self) -> InjectQ:
        """Setup InjectQ container with configuration."""
        container = InjectQ()

        # Bind configuration
        container.bind(dict, self.config_data, name="config").singleton()

        # Database configuration
        db_config = DatabaseConfig(
            host=self.config_data["database"]["host"],
            port=self.config_data["database"]["port"],
            database=self.config_data["database"]["name"]
        )
        container.bind(DatabaseConfig, db_config).singleton()

        # API configuration
        api_config = ApiConfig(
            endpoint=self.config_data["api"]["endpoint"],
            timeout=self.config_data["api"]["timeout"]
        )
        container.bind(ApiConfig, api_config).singleton()

        return container

# Usage
config_manager = ConfigurationManager("production")
container = config_manager.setup_injectq_container()

Service Locator Migration

# Migrate from Service Locator pattern to proper DI
class ServiceLocatorMigration:
    """Help migrate from service locator pattern."""

    def __init__(self):
        self.container = InjectQ()
        self._setup_services()

    def _setup_services(self):
        """Setup services in InjectQ container."""
        # Instead of global service locator
        self.container.bind(DatabaseService, DatabaseService).singleton()
        self.container.bind(UserService, UserService)
        self.container.bind(EmailService, EmailService)

    def get_service(self, service_type):
        """Temporary method to ease migration."""
        return self.container.get(service_type)

    def migrate_to_injection(self):
        """Examples of proper dependency injection."""

        # โŒ Before: Service locator usage
        class BadUserController:
            def __init__(self, service_locator):
                self.service_locator = service_locator

            def create_user(self, user_data):
                user_service = self.service_locator.get_service(UserService)
                email_service = self.service_locator.get_service(EmailService)
                # Use services...

        # โœ… After: Proper dependency injection
        class GoodUserController:
            @inject
            def __init__(self, user_service: UserService, email_service: EmailService):
                self.user_service = user_service
                self.email_service = email_service

            def create_user(self, user_data):
                # Use injected services directly
                user = self.user_service.create_user(user_data)
                self.email_service.send_welcome_email(user)
                return user

# Usage
migration = ServiceLocatorMigration()

# Temporary service locator interface
user_service = migration.get_service(UserService)

# Properly injected controller
container = migration.container
container.bind(GoodUserController, GoodUserController)
controller = container.get(GoodUserController)

Factory Pattern Migration

# Migrate factory patterns
class FactoryMigration:
    """Migrate various factory patterns to InjectQ."""

    def __init__(self):
        self.container = InjectQ()
        self._setup_factories()

    def _setup_factories(self):
        """Setup factory patterns in InjectQ."""

        # Simple factory
        self.container.bind(DatabaseConnection, self.create_database_connection)

        # Abstract factory
        self.container.bind(ServiceFactory, ConcreteServiceFactory).singleton()

        # Factory method
        self.container.bind(UserService, self.create_user_service)

    def create_database_connection(self) -> DatabaseConnection:
        """Factory method for database connection."""
        config = self.container.get(DatabaseConfig)
        return DatabaseConnection(
            host=config.host,
            port=config.port,
            database=config.database
        )

    def create_user_service(self) -> UserService:
        """Factory method for user service."""
        db = self.container.get(DatabaseConnection)
        cache = self.container.get(CacheService)
        return UserService(db, cache)

# Abstract factory pattern
from abc import ABC, abstractmethod

class ServiceFactory(ABC):
    @abstractmethod
    def create_user_service(self) -> UserService:
        pass

    @abstractmethod
    def create_order_service(self) -> OrderService:
        pass

class ConcreteServiceFactory(ServiceFactory):
    @inject
    def __init__(self, container: InjectQ):
        self.container = container

    def create_user_service(self) -> UserService:
        return self.container.get(UserService)

    def create_order_service(self) -> OrderService:
        return self.container.get(OrderService)

# Usage
migration = FactoryMigration()
db_connection = migration.container.get(DatabaseConnection)
factory = migration.container.get(ServiceFactory)
user_service = factory.create_user_service()

๐Ÿงช Testing Migration

Universal Testing Pattern

# Universal testing approach for any framework migration
class UniversalTestingMigration:
    """Universal testing patterns for DI framework migration."""

    def create_test_container(self) -> InjectQ:
        """Create container for testing."""
        container = InjectQ()

        # Mock dependencies
        container.bind(DatabaseService, MockDatabaseService).singleton()
        container.bind(EmailService, MockEmailService).singleton()
        container.bind(CacheService, MockCacheService).singleton()

        # Real services under test
        container.bind(UserService, UserService)
        container.bind(OrderService, OrderService)

        return container

    def create_integration_test_container(self) -> InjectQ:
        """Create container for integration testing."""
        container = InjectQ()

        # Test database
        test_db_config = DatabaseConfig(
            host="localhost",
            port=5433,
            database="test_db"
        )
        container.bind(DatabaseConfig, test_db_config).singleton()
        container.bind(DatabaseService, DatabaseService).singleton()

        # Mock external services
        container.bind(EmailService, MockEmailService).singleton()

        # Real services
        container.bind(UserService, UserService)

        return container

# Mock implementations
class MockDatabaseService:
    def __init__(self):
        self.data = {}

    def save(self, key: str, value: dict):
        self.data[key] = value

    def find(self, key: str):
        return self.data.get(key)

class MockEmailService:
    def __init__(self):
        self.sent_emails = []

    def send_email(self, to: str, subject: str, body: str):
        self.sent_emails.append({
            "to": to,
            "subject": subject,
            "body": body
        })

# Test example
import unittest

class TestUserService(unittest.TestCase):
    def setUp(self):
        self.migration = UniversalTestingMigration()
        self.container = self.migration.create_test_container()

    def test_user_creation(self):
        user_service = self.container.get(UserService)
        user_data = {"name": "John Doe", "email": "john@example.com"}

        user = user_service.create_user(user_data)

        self.assertIsNotNone(user)
        self.assertEqual(user["name"], "John Doe")

        # Check if email was sent
        email_service = self.container.get(EmailService)
        self.assertEqual(len(email_service.sent_emails), 1)
        self.assertEqual(email_service.sent_emails[0]["to"], "john@example.com")

๐Ÿ“Š Performance Migration

Performance Optimization

# Performance optimization during migration
class PerformanceMigration:
    """Optimize performance during DI framework migration."""

    def __init__(self):
        self.container = InjectQ()
        self._optimize_container()

    def _optimize_container(self):
        """Apply performance optimizations."""

        # Use appropriate scopes
        self.container.bind(DatabaseConnection, DatabaseConnection).singleton()
        self.container.bind(CacheService, CacheService).singleton()
        self.container.bind(ConfigService, ConfigService).singleton()

        # Transient for lightweight services
        self.container.bind(UserService, UserService)  # Default: transient
        self.container.bind(OrderService, OrderService)

        # Scoped for request-specific services
        self.container.bind(RequestContext, RequestContext).scoped()
        self.container.bind(SessionService, SessionService).scoped()

    def benchmark_migration(self, old_framework_resolver, iterations: int = 1000):
        """Benchmark performance comparison."""
        import time

        # Benchmark old framework
        start_time = time.time()
        for _ in range(iterations):
            service = old_framework_resolver()
        old_time = (time.time() - start_time) * 1000  # ms

        # Benchmark InjectQ
        start_time = time.time()
        for _ in range(iterations):
            service = self.container.get(UserService)
        new_time = (time.time() - start_time) * 1000  # ms

        improvement = ((old_time - new_time) / old_time) * 100

        print(f"Performance Comparison ({iterations} iterations):")
        print(f"Old framework: {old_time:.2f}ms")
        print(f"InjectQ: {new_time:.2f}ms")
        print(f"Improvement: {improvement:.2f}%")

        return {
            "old_time": old_time,
            "new_time": new_time,
            "improvement_percent": improvement
        }

# Usage
migration = PerformanceMigration()

# Example old framework resolver
def old_framework_resolver():
    # Simulate old framework resolution
    import time
    time.sleep(0.001)  # Simulate slower resolution
    return UserService(DatabaseService(), EmailService())

# Benchmark
results = migration.benchmark_migration(old_framework_resolver)

๐ŸŽฏ Migration Summary

Universal Migration Steps

  1. Analysis Phase
  2. Identify current DI patterns
  3. Map services and dependencies
  4. Identify pain points and limitations

  5. Planning Phase

  6. Create migration timeline
  7. Plan testing strategy
  8. Identify performance requirements

  9. Implementation Phase

  10. Start with core services
  11. Migrate module by module
  12. Update tests continuously

  13. Optimization Phase

  14. Apply appropriate scopes
  15. Optimize performance
  16. Monitor memory usage

  17. Validation Phase

  18. Run comprehensive tests
  19. Performance benchmarking
  20. Production readiness check

Common Migration Patterns

  • Container Replacement: Replace old container with InjectQ
  • Binding Migration: Convert registration patterns to InjectQ bindings
  • Scope Optimization: Apply appropriate scopes for performance
  • Testing Enhancement: Use InjectQ's testing utilities
  • Async Upgrade: Add async support where beneficial

Benefits Across All Migrations

  • Consistent API: Unified interface across all services
  • Better Performance: Optimized resolution and memory usage
  • Enhanced Testing: Comprehensive testing utilities
  • Type Safety: Better type checking and IDE support
  • Async Support: Full async/await support
  • Profiling Tools: Built-in performance monitoring

Migration Best Practices

  1. Gradual Migration: Migrate incrementally, not all at once
  2. Test-Driven: Write tests before migrating production code
  3. Performance Monitoring: Benchmark before and after migration
  4. Documentation: Document migration decisions and patterns
  5. Team Training: Ensure team understands new patterns
  6. Rollback Plan: Have a plan to rollback if needed

This completes the migration guides section, providing comprehensive guidance for migrating from various dependency injection frameworks to InjectQ.