Где будешь внедрять зависимости на примере Onion архитектуры в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Внедрение зависимостей в Onion архитектуре
В Onion архитектуре внедрение зависимостей строится на принципе инверсии управления (IoC) и следует направлению зависимостей от внешних слоёв к внутренним. Давайте разберёмся, где и как это делается.
Структура слоёв
Onion архитектура состоит из слоёв (от центра к внешним):
- Domain (ядро) — бизнес-логика, интерфейсы
- Application — use cases, сценарии использования
- Infrastructure — реализация интерфейсов (БД, API, кэш)
- Presentation — контроллеры, HTTP endpoints, CLI
Где внедрять зависимости
1. На уровне Application (Use Cases)
Это правильное место для DI. Use Case должен зависеть от интерфейсов (abstract), которые определены в Domain слое.
from abc import ABC, abstractmethod
from dataclasses import dataclass
# Domain layer — интерфейсы
class UserRepository(ABC):
@abstractmethod
def get_by_id(self, user_id: str) -> dict:
pass
@abstractmethod
def save(self, user: dict) -> None:
pass
class EmailService(ABC):
@abstractmethod
def send(self, email: str, message: str) -> None:
pass
# Application layer — Use Case
@dataclass
class UpdateUserUseCase:
user_repo: UserRepository
email_service: EmailService
def execute(self, user_id: str, new_email: str) -> None:
user = self.user_repo.get_by_id(user_id)
user["email"] = new_email
self.user_repo.save(user)
self.email_service.send(new_email, "Email updated")
2. На уровне Infrastructure (реализация)
Инфраструктурный слой реализует интерфейсы из Domain.
# Infrastructure layer
class PostgresUserRepository(UserRepository):
def __init__(self, connection_string: str):
self.db = create_connection(connection_string)
def get_by_id(self, user_id: str) -> dict:
# SQL запрос
return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
def save(self, user: dict) -> None:
# UPDATE запрос
self.db.execute(f"UPDATE users SET email = {user[email]} WHERE id = {user[id]}")
class SmtpEmailService(EmailService):
def __init__(self, smtp_host: str, smtp_port: int):
self.smtp_host = smtp_host
self.smtp_port = smtp_port
def send(self, email: str, message: str) -> None:
# Отправка письма
pass
3. На уровне Presentation (сборка контейнера)
Презентационный слой собирает объекты и внедряет зависимости.
# Presentation layer — HTTP handler / CLI
from fastapi import FastAPI
from dependency_injector import containers, providers
# Контейнер зависимостей
class Container(containers.DeclarativeContainer):
# Конфигурация
config = providers.Configuration()
# Infrastructure
user_repo = providers.Singleton(
PostgresUserRepository,
connection_string=config.db_connection_string
)
email_service = providers.Singleton(
SmtpEmailService,
smtp_host=config.smtp_host,
smtp_port=config.smtp_port
)
# Application
update_user_use_case = providers.Factory(
UpdateUserUseCase,
user_repo=user_repo,
email_service=email_service
)
app = FastAPI()
container = Container()
container.config.from_env()
@app.post("/users/{user_id}/email")
async def update_user_email(user_id: str, new_email: str):
use_case = container.update_user_use_case()
use_case.execute(user_id, new_email)
return {"status": "updated"}
Направление зависимостей
Критически важно: зависимости направлены ВНУТРЬ
Presentation → Application → Infrastructure
↓
Domain (интерфейсы)
- Domain не знает о реализациях
- Application зависит от Domain интерфейсов, не от конкретных реализаций
- Infrastructure реализует Domain интерфейсы
- Presentation зависит от всех слоёв (собирает контейнер)
Пример без DI контейнера
Можно внедрять вручную, если проект маленький:
def main():
# Создаём реализации
user_repo = PostgresUserRepository("postgres://...")
email_service = SmtpEmailService("smtp.gmail.com", 587)
# Внедряем в use case
use_case = UpdateUserUseCase(user_repo, email_service)
# Используем
use_case.execute("user_123", "new@email.com")
Ключевые правила
✅ Делай так:
- Интерфейсы в Domain слое
- Реализации в Infrastructure
- Use Cases внедряют зависимости через конструктор
- Presentation собирает контейнер
❌ Не делай так:
- Не импортируй Infrastructure классы в Domain
- Не создавай глобальные синглтоны в бизнес-логике
- Не передавай зависимости через параметры функций (передавай через конструктор)
Этот подход обеспечивает тестируемость (легко мокировать), гибкость (легко менять реализацию) и чистоту архитектуры.