Dependency Injection

Dependency Injection (DI) is a design pattern that allows you to decouple the creation of an object from its dependencies. This makes it easier to test and maintain your code, as you can easily swap out implementations of dependencies without changing the code that uses them. In Alpha, the dependency_injector library is used to manage the dependencies of your repositories, services, and other components. By using a DI container, you can define how your components are created and how they depend on each other in a centralized location. This allows for greater flexibility and maintainability in your application, as you can easily change the implementation of a dependency without affecting the rest of your codebase. In this section, we will cover how to set up and use dependency injection in your application, including examples of how to configure the DI container and how to use it to manage your dependencies effectively.

Setting Up Dependency Injection

To set up dependency injection in your application, you can create a DI container using the dependency_injector library. This container will define how your components are created and how they depend on each other. You can use providers to specify how to create instances of your components, and you can also configure the container to manage the lifecycle of your dependencies. For example, you can use Factory providers to create new instances of a component each time it is requested, or you can use Singleton providers to create a single instance that is shared across the application. By configuring your DI container properly, you can ensure that your components are created with the correct dependencies and that you can easily swap out implementations when needed. This is especially useful for testing, as you can use mock implementations of your dependencies to isolate the code you want to test.

Example Usage

In this example, we define a DI container that includes a Database connector and an ObjectManagementService. The ObjectManagementService depends on a database, which is provided by the Database connector. By using the DI container, we can easily manage the dependencies of our components and swap out implementations when needed. For instance, if we want to use a different database connector or a mock service for testing, we can simply change the configuration of the DI container without modifying the code that uses these components. This makes our application more flexible and easier to maintain.

from dependency_injector import containers, providers
from my_app.connectors import Database
from my_app.services import ObjectManagementService

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

    # Connectors
    database = providers.Singleton(
        Database,
        host=config.database.host,
        port=config.database.port,
        username=config.database.username,
        password=config.database.password,
        db_name=config.database.db_name,
        db_type=config.database.db_type,
    )

    # Services
    object_management_service = providers.Factory(
        ObjectManagementService,
        database=database,
    )

By defining a separate function to instantiate the Container, you create a convenient way to configure and wire the components.

import os

from my_app.containers.container import Container


def init_container() -> Container:
    CONFIG_PATH = os.getenv("CONFIG_YAML", "./config.yaml")

    container = Container()
    container.config.from_yaml(CONFIG_PATH)

    # Override database config with environment variables if they exist
    container.config.database.host.from_env(
        'DATABASE_HOST', container.config.database.host()
    )
    container.config.database.port.from_env(
        'DATABASE_PORT', container.config.database.port()
    )
    container.config.database.username.from_env(
        'DATABASE_USERNAME', container.config.database.username()
    )
    container.config.database.password.from_env(
        'DATABASE_PASSWORD', container.config.database.password()
    )
    container.config.database.db_name.from_env(
        'DATABASE_NAME', container.config.database.db_name()
    )
    container.config.database.db_type.from_env(
        'DATABASE_TYPE', container.config.database.db_type()
    )

    container.wire(modules=[__name__])
    return container