← Назад к вопросам
Как осуществляется изоляция компонента?
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()
Итоговый чеклист изоляции компонентов
- Разделение по слоям: domain → application → infrastructure → presentation
- Инъекция зависимостей: передавай зависимости через конструктор
- Интерфейсы вместо реализаций: используй ABC или Protocol
- Модульная структура: каждый компонент в отдельном модуле
- Отсутствие циклических зависимостей: A → B, но не B → A
- Конфигурация для разных окружений: не хардкодь значения
- Тестируемость: легко замочить зависимости в тестах
Правильная изоляция делает код поддерживаемым, тестируемым и масштабируемым.