← Назад к вопросам
Какие плюсы и минусы чистой архитектуры?
2.4 Senior🔥 221 комментариев
#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Clean Architecture: Принципы, Плюсы и Минусы
Clean Architecture (чистая архитектура), предложенная Робертом Мартином, — это подход к организации кода, где бизнес-логика полностью независима от деталей реализации (БД, фреймворки, UI).
Структура Clean Architecture
┌─────────────────────────────────────────────┐
│ Presentation Layer (UI) │ Фреймворк зависит от здесь
│ (Controllers, Handlers, Views) │ всего остального
├─────────────────────────────────────────────┤
│ Application Layer (Use Cases) │ Зависимости только внутрь
│ (Business Logic Orchestration, DTOs) │
├─────────────────────────────────────────────┤
│ Domain Layer (Business Rules) │
│ (Entities, Value Objects, Interfaces) │ Не зависит ни от чего
├─────────────────────────────────────────────┤
│ Infrastructure Layer (Implementation) │ Реализация интерфейсов
│ (Database, External APIs, Email) │ из Domain
└─────────────────────────────────────────────┘
Правило зависимостей: → → → (только внутрь, не наружу)
Пример на Python:
# domain/entities.py — не зависит ни от чего
from dataclasses import dataclass
from abc import ABC
@dataclass
class User:
id: str
name: str
email: str
def validate(self):
if not self.email or '@' not in self.email:
raise ValueError("Invalid email")
class UserRepository(ABC):
async def save(self, user: User) -> None: ...
async def find_by_id(self, user_id: str) -> User: ...
# application/use_cases.py — зависит только от domain
from domain.entities import User, UserRepository
class CreateUserUseCase:
def __init__(self, user_repo: UserRepository):
self.user_repo = user_repo
async def execute(self, name: str, email: str) -> User:
user = User(id=uuid4(), name=name, email=email)
user.validate()
await self.user_repo.save(user)
return user
# infrastructure/repositories.py — реализует интерфейсы domain
from sqlalchemy.orm import Session
from domain.entities import User, UserRepository
from infrastructure.models import UserModel
class SQLAlchemyUserRepository(UserRepository):
def __init__(self, db: Session):
self.db = db
async def save(self, user: User) -> None:
model = UserModel(id=user.id, name=user.name, email=user.email)
self.db.add(model)
await self.db.commit()
async def find_by_id(self, user_id: str) -> User:
model = await self.db.query(UserModel).filter(
UserModel.id == user_id
).first()
return User(id=model.id, name=model.name, email=model.email)
# presentation/handlers.py — зависит от application
from fastapi import APIRouter
from application.use_cases import CreateUserUseCase
router = APIRouter()
@router.post("/users")
async def create_user(name: str, email: str, use_case: CreateUserUseCase):
user = await use_case.execute(name, email)
return {"id": user.id, "name": user.name}
Плюсы Clean Architecture
1. Независимость от фреймворков
Бизнес-логика не зависит от Django, FastAPI, Flask и т.д.:
# Domain code работает везде, в любом фреймворке
class CalculateUserScore:
def execute(self, user: User) -> int:
# Чистая логика, без HTTP, БД или фреймворка
return user.posts_count * 10 + user.comments_count * 5
# Легко мигрировать между фреймворками
# FastAPI версия
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}/score")
async def get_user_score(user_id: str, use_case: CalculateUserScore):
return use_case.execute(user)
# Flask версия (та же логика)
from flask import Flask
app = Flask(__name__)
@app.route("/users/<user_id>/score")
def get_user_score(user_id: str):
return use_case.execute(user)
2. Простота тестирования
Можно тестировать бизнес-логику без БД, HTTP, файловой системы:
# Тест не нужна реальная БД
class MockUserRepository(UserRepository):
def __init__(self):
self.saved_users = []
async def save(self, user: User) -> None:
self.saved_users.append(user)
async def find_by_id(self, user_id: str) -> User:
return next(u for u in self.saved_users if u.id == user_id)
async def test_create_user():
repo = MockUserRepository()
use_case = CreateUserUseCase(repo)
user = await use_case.execute("Alice", "alice@example.com")
assert user.name == "Alice"
assert len(repo.saved_users) == 1
# Тест быстрый, не нужна БД, не нужен HTTP!
3. Легко менять детали реализации
Можно менять БД с PostgreSQL на MongoDB без изменения логики:
# Интерфейс остается прежним
class UserRepository(ABC):
async def save(self, user: User) -> None: ...
# PostgreSQL реализация
class PostgreSQLUserRepository(UserRepository):
async def save(self, user: User) -> None:
# SQL...
pass
# MongoDB реализация
class MongoDBUserRepository(UserRepository):
async def save(self, user: User) -> None:
# MongoDB...
pass
# Логика не меняется!
class CreateUserUseCase:
def __init__(self, user_repo: UserRepository):
# Не важно какая реализация
self.user_repo = user_repo
4. Предсказуемость и надежность
Ясная структура упрощает понимание:
Проблема в коде? → Смотри в domain (бизнес-правила)
Проблема в HTTP? → Смотри в presentation
Проблема с БД? → Смотри в infrastructure
Проблема в обработке? → Смотри в application
5. Масштабируемость
Легко добавлять новые функции и интеграции:
# Была функция GetUser
class GetUserUseCase:
async def execute(self, user_id: str) -> User: ...
# Добавляем GetUserWithPosts без изменения существующего кода
class GetUserWithPostsUseCase:
def __init__(self, user_repo: UserRepository, post_repo: PostRepository):
self.user_repo = user_repo
self.post_repo = post_repo
async def execute(self, user_id: str) -> dict:
user = await self.user_repo.find_by_id(user_id)
posts = await self.post_repo.find_by_user(user_id)
return {"user": user, "posts": posts}
Минусы Clean Architecture
1. Оверинжиниринг для малых проектов
Для простого скрипта создание 4 слоев избыточно:
# Простой CRUD — оверкомплицировано
# domain/user.py
class User:
def __init__(self, id, name): ...
# application/use_cases.py
class CreateUserUseCase: ...
# infrastructure/repositories.py
class UserRepository: ...
# presentation/handlers.py
@router.post("/users")
async def create_user(): ...
# Вместо простого
@router.post("/users")
async def create_user(db: Session, name: str):
user = User(name=name)
db.add(user)
db.commit()
return user
2. Много boilerplate кода и абстракций
# Много интерфейсов и классов для простой функции
# Интерфейс
class NotificationService(ABC):
@abstractmethod
async def send(self, message: str) -> None: ...
# Реализация 1
class EmailNotificationService(NotificationService):
async def send(self, message: str) -> None:
# email...
pass
# Реализация 2
class SMSNotificationService(NotificationService):
async def send(self, message: str) -> None:
# sms...
pass
# Use Case
class NotifyUserUseCase:
def __init__(self, notification_service: NotificationService):
self.service = notification_service
async def execute(self, user_id: str) -> None:
await self.service.send("Hello")
# Для простого уведомления!
3. Производительность: много слоев = больше вызовов
# Простой запрос проходит через 4 слоя
# Слой 1: Presentation
@router.get("/users/{user_id}")
async def get_user(user_id: str, get_user_use_case: GetUserUseCase):
return await get_user_use_case.execute(user_id)
# Слой 2: Application
class GetUserUseCase:
async def execute(self, user_id: str):
return await self.repository.find_by_id(user_id)
# Слой 3: Infrastructure
class UserRepository:
async def find_by_id(self, user_id: str):
return await self.db.query(...)
# Слой 4: Database
# Запрос
# Для простого SELECT может быть избыточно
4. Сложность отладки через много слоев
# Ошибка где-то глубоко
# Нужно пройти через все слои для отладки
@router.post("/users")
async def create_user(data, use_case):
# 1. Presentation layer
return await use_case.execute(data)
class CreateUserUseCase:
async def execute(self, data):
# 2. Application layer
user = User(**data)
return await self.repo.save(user)
class UserRepository:
async def save(self, user):
# 3. Infrastructure layer
model = UserModel(**user.dict())
self.db.add(model)
await self.db.commit()
# 4. Database layer
return model
5. Типизация может быть излишней
# Много интерфейсов может замедлить разработку
class PaymentProcessor(ABC):
@abstractmethod
async def process_payment(self, amount: float) -> dict: ...
class StripePaymentProcessor(PaymentProcessor):
async def process_payment(self, amount: float) -> dict:
# реальная логика
pass
class MockPaymentProcessor(PaymentProcessor):
async def process_payment(self, amount: float) -> dict:
# для тестов
pass
# Для простого payment это может быть overengineering
Когда использовать Clean Architecture
Идеально подходит для:
# 1. Долгосрочные проекты
# - Проект будет развиваться несколько лет
# - Много разработчиков
# - Частые изменения требований
# 2. Сложная бизнес-логика
# - Много use cases
# - Сложные правила
# - Много интеграций
# 3. Когда нужна тестируемость
# - Требуется 90%+ coverage
# - Дорогой code review
# - Production system
Избегай для:
# 1. MVP и прототипы
# - Быстрое создание
# - Неясные требования
# - Может измениться полностью
# 2. Простые скрипты
# - Один модуль
# - Простая логика
# - Не требует расширения
# 3. CRUD приложения
# - Простые операции
# - Минимальная бизнес-логика
Практический баланс
# Гибридный подход: только нужные слои
class Order:
"""Domain entity"""
def __init__(self, user_id: str, items: list):
self.user_id = user_id
self.items = items
def validate(self):
if not self.items:
raise ValueError("Order must have items")
class OrderRepository:
"""Infrastructure"""
async def save(self, order: Order) -> None:
db.add(OrderModel(**order.dict()))
await db.commit()
@router.post("/orders")
async def create_order(data: dict, repo: OrderRepository):
"""Presentation + Application in one"""
order = Order(**data)
order.validate()
await repo.save(order)
return {"id": order.id}
# Не излишне, но не лишено структуры
Итог
| Аспект | Clean Architecture |
|---|---|
| Тестируемость | Отлично |
| Поддерживаемость | Отлично |
| Производительность | Может быть медленнее |
| Скорость разработки | Медленнее изначально |
| Масштабируемость | Отлично |
| Простота | Сложнее |
| Best for | Enterprise, долгосрочные проекты |
| Avoid for | MVP, простые скрипты |
Рекомендация: Используй Clean Architecture в сочетании с YAGNI (You Ain't Gonna Need It) — добавляй абстракции только когда они действительно нужны, а не "на будущее".