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

Как осуществляется изоляция компонента?

2.0 Middle🔥 191 комментариев
#Архитектура и паттерны

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

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

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

Изоляция компонентов в Python: архитектурные подходы

Изоляция компонентов — это ключевая практика чистой архитектуры. За 10+ лет я видел множество подходов: от плохо интегрированного кода до идеально структурированных систем. Расскажу о наиболее эффективных методах.

1. Архитектурная слоистость (Layered Architecture)

Это разделение приложения по слоям ответственности:

# project/
# ├── presentation/       # REST API, CLI, Web
# ├── application/        # Бизнес-логика, use cases
# ├── domain/             # Сущности, интерфейсы
# └── infrastructure/      # БД, внешние API, сеть

# domain/user.py — чистый домен, без зависимостей
from dataclasses import dataclass
from abc import ABC, abstractmethod

@dataclass
class User:
    """Сущность домена — не зависит от框架, БД, API"""
    id: str
    email: str
    name: str
    
    def is_valid_email(self) -> bool:
        return "@" in self.email

class UserRepository(ABC):
    """Интерфейс репозитория — определяет контракт"""
    
    @abstractmethod
    def get_by_id(self, user_id: str) -> User:
        pass
    
    @abstractmethod
    def save(self, user: User) -> None:
        pass

# application/use_cases.py — бизнес-логика
from domain.user import User, UserRepository

class RegisterUserUseCase:
    """Use case — ордеживает бизнес-процесс"""
    
    def __init__(self, repository: UserRepository):
        self.repository = repository  # Инъекция зависимости
    
    def execute(self, email: str, name: str) -> User:
        # Создаём новую сущность
        user = User(id="new", email=email, name=name)
        
        # Проверяем валидность
        if not user.is_valid_email():
            raise ValueError("Invalid email")
        
        # Сохраняем через репозиторий
        self.repository.save(user)
        return user

# infrastructure/repositories.py — конкретная реализация
from sqlalchemy.orm import Session
from domain.user import User, UserRepository

class SQLAlchemyUserRepository(UserRepository):
    """Конкретная реализация для PostgreSQL"""
    
    def __init__(self, session: Session):
        self.session = session
    
    def get_by_id(self, user_id: str) -> User:
        db_user = self.session.query(UserModel).filter_by(id=user_id).first()
        return User(
            id=db_user.id,
            email=db_user.email,
            name=db_user.name
        )
    
    def save(self, user: User) -> None:
        db_user = UserModel(id=user.id, email=user.email, name=user.name)
        self.session.add(db_user)
        self.session.commit()

# presentation/api.py — REST endpoint
from fastapi import APIRouter
from application.use_cases import RegisterUserUseCase

router = APIRouter()

@router.post("/users")
def register_user(email: str, name: str, use_case: RegisterUserUseCase):
    user = use_case.execute(email, name)
    return {"id": user.id, "email": user.email}

2. Инъекция зависимостей (Dependency Injection)

Это позволяет менять реализацию без изменения компонента:

from abc import ABC, abstractmethod
from typing import Protocol

# Определяем контракты (интерфейсы)
class EmailService(ABC):
    @abstractmethod
    def send(self, to: str, subject: str, body: str) -> bool:
        pass

class SMTPEmailService(EmailService):
    def send(self, to: str, subject: str, body: str) -> bool:
        # Реальная отправка по SMTP
        return True

class MockEmailService(EmailService):
    def send(self, to: str, subject: str, body: str) -> bool:
        # Для тестирования
        return True

# Компонент, который не знает деталей реализации
class UserNotificationService:
    def __init__(self, email_service: EmailService):
        self.email_service = email_service  # Инъекция!
    
    def notify_user_registered(self, email: str, name: str) -> None:
        self.email_service.send(
            to=email,
            subject="Welcome!",
            body=f"Hello, {name}!"
        )

# Использование в production
smtp_service = SMTPEmailService()
notifier_prod = UserNotificationService(smtp_service)

# Использование в тестах
mock_service = MockEmailService()
notifier_test = UserNotificationService(mock_service)

3. Модульная архитектура

Кажный компонент в отдельном модуле:

project/
├── modules/
│   ├── users/
│   │   ├── domain/
│   │   │   ├── __init__.py
│   │   │   ├── entities.py      # User сущность
│   │   │   └── repositories.py  # UserRepository интерфейс
│   │   ├── application/
│   │   │   ├── __init__.py
│   │   │   └── use_cases.py     # RegisterUser, GetUser
│   │   ├── infrastructure/
│   │   │   ├── __init__.py
│   │   │   └── repositories.py  # SQLAlchemy реализация
│   │   └── presentation/
│   │       ├── __init__.py
│   │       └── routes.py        # FastAPI endpoints
│   │
│   ├── posts/
│   │   ├── domain/
│   │   ├── application/
│   │   ├── infrastructure/
│   │   └── presentation/
│   │
│   └── comments/
│       ├── domain/
│       ├── application/
│       ├── infrastructure/
│       └── presentation/
└── main.py  # Ассемблирование приложения
# modules/users/__init__.py
from .domain.entities import User
from .application.use_cases import RegisterUserUseCase

__all__ = ["User", "RegisterUserUseCase"]

# modules/users/domain/entities.py
from dataclasses import dataclass

@dataclass
class User:
    id: str
    email: str

# modules/users/domain/repositories.py
from abc import ABC, abstractmethod
from .entities import User

class UserRepository(ABC):
    @abstractmethod
    def get_by_id(self, user_id: str) -> User:
        pass

# modules/users/application/use_cases.py
from ..domain.entities import User
from ..domain.repositories import UserRepository

class GetUserUseCase:
    def __init__(self, repository: UserRepository):
        self.repository = repository
    
    def execute(self, user_id: str) -> User:
        return self.repository.get_by_id(user_id)

4. Изоляция через интерфейсы (Protocols)

from typing import Protocol
from abc import abstractmethod

# Protocol — легкий способ определить интерфейс
class DatabaseConnection(Protocol):
    @abstractmethod
    def execute(self, query: str) -> list:
        ...
    
    @abstractmethod
    def close(self) -> None:
        ...

class DataService:
    """Сервис не зависит от конкретной БД"""
    
    def __init__(self, db: DatabaseConnection):
        self.db = db
    
    def get_users(self) -> list:
        return self.db.execute("SELECT * FROM users")

# Работает с любой реализацией DatabaseConnection
class PostgresConnection:
    def execute(self, query: str) -> list:
        # PostgreSQL реализация
        pass
    
    def close(self) -> None:
        pass

class MongoConnection:
    def execute(self, query: str) -> list:
        # MongoDB реализация
        pass
    
    def close(self) -> None:
        pass

# Меняем реализацию без изменения DataService
db_postgres = PostgresConnection()
db_mongo = MongoConnection()
service1 = DataService(db_postgres)
service2 = DataService(db_mongo)  # Работает так же!

5. Контейнер зависимостей (DI Container)

from typing import Any, Dict, Callable

class DIContainer:
    """Контейнер для управления зависимостями"""
    
    def __init__(self):
        self._singletons: Dict[str, Any] = {}
        self._factories: Dict[str, Callable] = {}
    
    def register_singleton(self, name: str, instance: Any) -> None:
        """Регистрируем единственный экземпляр"""
        self._singletons[name] = instance
    
    def register_factory(self, name: str, factory: Callable) -> None:
        """Регистрируем фабрику для создания объектов"""
        self._factories[name] = factory
    
    def get(self, name: str) -> Any:
        """Получить объект из контейнера"""
        if name in self._singletons:
            return self._singletons[name]
        elif name in self._factories:
            return self._factories[name]()
        else:
            raise ValueError(f"Unknown service: {name}")

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

# Регистрируем зависимости
container.register_singleton("email_service", SMTPEmailService())
container.register_factory(
    "user_notifier",
    lambda: UserNotificationService(container.get("email_service"))
)

# Получаем готовый объект
notifier = container.get("user_notifier")

6. Конфигурация для разных окружений

from enum import Enum
from typing import Optional

class Environment(Enum):
    DEV = "dev"
    TEST = "test"
    PROD = "prod"

class Config:
    def __init__(self, env: Environment):
        self.env = env
    
    def get_database_url(self) -> str:
        urls = {
            Environment.DEV: "sqlite:///dev.db",
            Environment.TEST: "sqlite:///:memory:",
            Environment.PROD: "postgresql://user:pass@prod-server/db",
        }
        return urls[self.env]
    
    def get_email_service(self) -> EmailService:
        if self.env == Environment.PROD:
            return SMTPEmailService()
        else:
            return MockEmailService()
    
    def should_log_debug(self) -> bool:
        return self.env in (Environment.DEV, Environment.TEST)

# Использование
env = os.getenv("APP_ENV", "dev")
config = Config(Environment(env))
email_service = config.get_email_service()

Итоговый чеклист изоляции компонентов

  1. Разделение по слоям: domain → application → infrastructure → presentation
  2. Инъекция зависимостей: передавай зависимости через конструктор
  3. Интерфейсы вместо реализаций: используй ABC или Protocol
  4. Модульная структура: каждый компонент в отдельном модуле
  5. Отсутствие циклических зависимостей: A → B, но не B → A
  6. Конфигурация для разных окружений: не хардкодь значения
  7. Тестируемость: легко замочить зависимости в тестах

Правильная изоляция делает код поддерживаемым, тестируемым и масштабируемым.

Как осуществляется изоляция компонента? | PrepBro