Как и где происходит внедрение зависимостей (DI) в луковой архитектуре (Onion Architecture) на Python?

Ответ

В луковой архитектуре внедрение зависимостей (Dependency Injection, DI) происходит на границах слоев и следует Принципу инверсии зависимостей (Dependency Inversion Principle). Внутренние слои не зависят от внешних; вместо этого все слои зависят от абстракций.

Механизм DI

  1. Определение Абстракции (Порт): Во внутреннем слое (например, application или domain) определяется абстрактный интерфейс. В Python это обычно abc.ABC или typing.Protocol.
  2. Реализация (Адаптер): Во внешнем, инфраструктурном слое (infrastructure) создается конкретный класс, который реализует этот интерфейс (например, для работы с базой данных или внешним API).
  3. Внедрение: Зависимость (конкретная реализация) передается в конструктор класса внутреннего слоя. Это позволяет внутреннему слою работать с абстракцией, не зная о деталях реализации.
  4. Сборка (Composition Root): В самой внешней точке приложения (например, в main.py или с помощью DI-контейнера) создаются экземпляры конкретных реализаций и внедряются в сервисы.

Пример на Python

from abc import ABC, abstractmethod

# === Domain Layer: Сущность ===
class User:
    def __init__(self, name: str):
        self.name = name

# === Application Layer: Абстракция (Порт) ===
class UserRepository(ABC):
    @abstractmethod
    def get_user(self, user_id: int) -> User:
        ...

# === Infrastructure Layer: Конкретная реализация (Адаптер) ===
class PostgresUserRepository(UserRepository):
    def get_user(self, user_id: int) -> User:
        # Логика для получения пользователя из PostgreSQL
        print(f"Fetching user {user_id} from PostgreSQL...")
        return User(name="John Doe")

# === Application Layer: Сервис, использующий абстракцию ===
class UserService:
    # Зависимость внедряется через конструктор
    def __init__(self, user_repo: UserRepository):
        self._user_repo = user_repo

    def process_user(self, user_id: int):
        user = self._user_repo.get_user(user_id)
        print(f"Processing user: {user.name}")

# === Composition Root: Точка сборки приложения ===
if __name__ == "__main__":
    # Создаем конкретную реализацию
    db_repository = PostgresUserRepository()

    # Внедряем ее в сервис
    user_service = UserService(user_repo=db_repository)

    # Используем сервис
    user_service.process_user(123)