← Назад к вопросам

Что такое направление зависимости?

1.8 Middle🔥 151 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Dependency Direction — Направление зависимостей в архитектуре

Направление зависимостей (Dependency Direction) — это принцип архитектуры, описывающий, какие модули и слои имеют право зависеть от других. Основное правило: зависимости должны указывать в сторону более высокоуровневых абстракций, а не от низкоуровневых деталей. Это ключевой принцип Clean Architecture и SOLID.

Основной принцип: Dependency Inversion Principle (DIP)

Dependency Inversion Principle гласит:

  1. Высокоуровневые модули НЕ должны зависеть от низкоуровневых модулей
  2. Оба должны зависеть от абстракций (интерфейсы, протоколы)
  3. Абстракции НЕ должны зависеть от деталей
  4. Детали ДОЛЖНЫ зависеть от абстракций

Антипаттерн: неправильное направление зависимостей

# ПЛОХО: BusinessLogic зависит от Database (низкоуровневая деталь)

class Database:
    """Конкретная реализация БД"""
    def get_user(self, user_id: int):
        # SQL запрос...
        return {'id': user_id, 'name': 'Alice'}

class UserService:
    """Бизнес-логика зависит от конкретной реализации БД"""
    
    def __init__(self):
        self.db = Database()  # Жёсткая связь!
    
    def get_user_with_orders(self, user_id: int):
        user = self.db.get_user(user_id)
        # ...
        return user

# Проблемы:
# 1. Невозможно поменять реализацию БД (PostgreSQL → MySQL)
# 2. Невозможно протестировать UserService (нужна реальная БД)
# 3. UserService зависит от деталей реализации Database

Правильное направление: зависимость от абстракции

from abc import ABC, abstractmethod
from typing import Protocol, Optional

# ХОРОШО: определяем абстракцию (интерфейс)
class UserRepository(ABC):
    """Абстракция для работы с пользователями"""
    
    @abstractmethod
    def get_user(self, user_id: int) -> dict:
        pass
    
    @abstractmethod
    def save_user(self, user: dict) -> None:
        pass

# Конкретные реализации зависят от абстракции
class PostgresUserRepository(UserRepository):
    def get_user(self, user_id: int) -> dict:
        # SQL запрос к PostgreSQL
        return {'id': user_id, 'name': 'Alice'}
    
    def save_user(self, user: dict) -> None:
        # SQL INSERT/UPDATE
        pass

class MySQLUserRepository(UserRepository):
    def get_user(self, user_id: int) -> dict:
        # SQL запрос к MySQL
        return {'id': user_id, 'name': 'Alice'}
    
    def save_user(self, user: dict) -> None:
        # SQL INSERT/UPDATE
        pass

# Бизнес-логика зависит ТОЛЬКО от абстракции
class UserService:
    def __init__(self, repository: UserRepository):  # Инъекция абстракции!
        self.repository = repository
    
    def get_user_with_orders(self, user_id: int):
        user = self.repository.get_user(user_id)
        # ...
        return user

# Использование
pg_repo = PostgresUserRepository()
service = UserService(pg_repo)
user = service.get_user_with_orders(1)

# Легко поменять на MySQL
mysql_repo = MySQLUserRepository()
service = UserService(mysql_repo)  # Никаких изменений в UserService!

Слоистая архитектура и направление зависимостей

В Clean Architecture есть строгое правило направления зависимостей:

Презентация (UI, API) ← Внешние слои зависят от внутренних
                            |
                            v
Прилож. (Use Cases) ← Средние слои зависят от доменных
                            |
                            v
Домен (Entities, Services) ← Внутренние слои НЕ зависят ни от кого
                            |
                            v
Infrastructure (Database)

ПРАВИЛО НАПРАВЛЕНИЯ:
presentation → application → domain ← infrastructure

Пример нарушения архитектуры

# ПЛОХО: Domain зависит от Infrastructure

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class User(Base):  # Это Entity, должен быть в Domain
    __tablename__ = 'users'  # НО зависит от SQLAlchemy (Infrastructure!)
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100))

# Проблемы:
# 1. Domain слой зависит от ORM (SQLAlchemy)
# 2. Невозможно использовать User без SQLAlchemy
# 3. Entity смешан с деталями хранения

Правильная архитектура с инверсией зависимостей

# ХОРОШО: разделение ответственности

# Domain слой (НЕ зависит ни от чего!)
class User:
    """Pure Domain Entity"""
    
    def __init__(self, id: int, name: str, email: str):
        self.id = id
        self.name = name
        self.email = email
    
    def change_name(self, new_name: str):
        if not new_name:
            raise ValueError('Name cannot be empty')
        self.name = new_name

# Application слой (зависит от Domain)
from abc import ABC, abstractmethod

class UserRepository(ABC):
    @abstractmethod
    def find_by_id(self, user_id: int) -> User:
        pass
    
    @abstractmethod
    def save(self, user: User) -> None:
        pass

class UpdateUserUseCase:
    def __init__(self, repository: UserRepository):
        self.repository = repository
    
    def execute(self, user_id: int, new_name: str):
        user = self.repository.find_by_id(user_id)
        user.change_name(new_name)
        self.repository.save(user)
        return user

# Infrastructure слой (зависит от Application и Domain)
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base, Session

Base = declarative_base()

class UserORM(Base):
    """ORM модель (только для хранения)"""
    __tablename__ = 'users'
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100))
    email = Column(String(100))

class SQLAlchemyUserRepository(UserRepository):
    def __init__(self, session: Session):
        self.session = session
    
    def find_by_id(self, user_id: int) -> User:
        orm_user = self.session.query(UserORM).filter_by(id=user_id).first()
        return User(orm_user.id, orm_user.name, orm_user.email)
    
    def save(self, user: User) -> None:
        orm_user = self.session.query(UserORM).filter_by(id=user.id).first()
        orm_user.name = user.name
        self.session.commit()

# Presentation слой (зависит от Application)
from fastapi import FastAPI

app = FastAPI()

@app.put('/api/users/{user_id}')
def update_user(user_id: int, new_name: str):
    repository = SQLAlchemyUserRepository(session)
    use_case = UpdateUserUseCase(repository)
    user = use_case.execute(user_id, new_name)
    return {'id': user.id, 'name': user.name}

Инверсия контроля (IoC) для управления зависимостями

from typing import Any

# Контейнер зависимостей (Dependency Injection Container)
class DIContainer:
    def __init__(self):
        self._services = {}
    
    def register(self, name: str, factory):
        """Регистрируем фабрику для создания сервиса"""
        self._services[name] = factory
    
    def get(self, name: str) -> Any:
        """Получаем экземпляр сервиса"""
        if name not in self._services:
            raise ValueError(f'Service {name} not registered')
        return self._services[name]()

# Использование
container = DIContainer()

# Регистрируем зависимости
container.register(
    'user_repository',
    lambda: SQLAlchemyUserRepository(session)
)

container.register(
    'update_user_use_case',
    lambda: UpdateUserUseCase(container.get('user_repository'))
)

# Получаем use case с уже внедрёнными зависимостями
use_case = container.get('update_user_use_case')
user = use_case.execute(1, 'New Name')

Правило: зависимости должны указывать «внутрь»

ПРАВИЛЬНОЕ НАПРАВЛЕНИЕ (Clean Architecture):
Внешние слои (детали реализации)
Presentation ───────►
Application ──────►
Domain (бизнес-логика, независима)

НЕПРАВИЛЬНОЕ НАПРАВЛЕНИЕ (плохая архитектура):
Domain ──────────► Application ──► Presentation
(зависит от деталей реализации)

Тестирование с правильным направлением зависимостей

import unittest
from unittest.mock import Mock

class TestUpdateUserUseCase(unittest.TestCase):
    def test_update_user_successfully(self):
        # Mock repository (легко подменить благодаря DIP!)
        mock_repo = Mock(spec=UserRepository)
        user = User(1, 'Alice', 'alice@example.com')
        mock_repo.find_by_id.return_value = user
        
        # Создаём use case с mock'ом
        use_case = UpdateUserUseCase(mock_repo)
        
        # Выполняем
        result = use_case.execute(1, 'Alice Smith')
        
        # Проверяем
        self.assertEqual(result.name, 'Alice Smith')
        mock_repo.find_by_id.assert_called_once_with(1)
        mock_repo.save.assert_called_once()

if __name__ == '__main__':
    unittest.main()

Резюме: Направление зависимостей — это принцип, что зависимости должны указывать от конкретных реализаций к абстракциям, а не наоборот. Правильное направление обеспечивает:

  • Слабую связанность (можно менять реализацию)
  • Простоту тестирования (можно использовать mock'и)
  • Гибкость (легко добавлять новые реализации)
  • Чистую архитектуру (разделение слоёв)
Что такое направление зависимости? | PrepBro