← Назад к вопросам
Что такое направление зависимости?
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 гласит:
- Высокоуровневые модули НЕ должны зависеть от низкоуровневых модулей
- Оба должны зависеть от абстракций (интерфейсы, протоколы)
- Абстракции НЕ должны зависеть от деталей
- Детали ДОЛЖНЫ зависеть от абстракций
Антипаттерн: неправильное направление зависимостей
# ПЛОХО: 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'и)
- Гибкость (легко добавлять новые реализации)
- Чистую архитектуру (разделение слоёв)