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

Какую проблему решает чистая архитектура?

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

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

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

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

Какую проблему решает чистая архитектура

Чистая архитектура (Clean Architecture) решает целый класс проблем, связанных с организацией кода и структурой приложения. Её главная цель — создать масштабируемое, тестируемое и maintainable приложение, которое независимо от деталей реализации (фреймворки, БД, UI).

Основная проблема: Spaghetti Code

Когда архитектуры нет, получается "спагетти-код":

# ❌ БЕЗ архитектуры (все в одном файле)
from flask import Flask, jsonify, request
import sqlite3
import hashlib

app = Flask(__name__)

@app.route('/register', methods=['POST'])
def register():
    # Бизнес-логика
    data = request.json
    
    # Валидация (смешана с HTTP)
    if not data.get('email') or len(data['password']) < 8:
        return jsonify({'error': 'Invalid data'}), 400
    
    # Работа с БД (смешана с бизнес-логикой)
    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    
    # Хеширование пароля (криптография внутри маршрута)
    hashed = hashlib.sha256(data['password'].encode()).hexdigest()
    
    # SQL запрос (смешано с логикой)
    cursor.execute(
        "INSERT INTO users (email, password) VALUES (?, ?)",
        (data['email'], hashed)
    )
    conn.commit()
    conn.close()
    
    return jsonify({'status': 'ok'}), 201

# ПРОБЛЕМЫ:
# - Нельзя протестировать без БД
# - Нельзя переключиться с Flask на FastAPI без переписывания логики
# - Логика разбросана по разным концернам
# - Сложно добавить новый способ аутентификации
# - Трудно читать и поддерживать

Проблемы, которые решает Clean Architecture

1. Зависимость от фреймворков и библиотек

Проблема: Когда весь код зависит от Flask/Django/FastAPI, нельзя переключиться без переписывания всего:

# ❌ Код зависит от Flask
@app.route('/users/<user_id>')
def get_user(user_id):
    return jsonify({'id': user_id})

# Захотели переключиться на FastAPI? Переписывай всё!

# ✅ Clean Architecture: логика независима от фреймворка
class UserService:
    def get_user(self, user_id: int) -> User:
        return self.repository.find_by_id(user_id)

# Используется и в Flask, и в FastAPI, и в CLI
flask_route = lambda user_id: UserService().get_user(user_id)
fastapi_endpoint = lambda user_id: UserService().get_user(user_id)

2. Сложность тестирования

Проблема: Нельзя протестировать бизнес-логику без реальной БД:

# ❌ Тестировать сложно (нужна реальная БД)
def test_register():
    # Нужно создать SQLite БД, инициализировать схему, cleanup...
    # Тесты медленные и хрупкие
    response = client.post('/register', json={...})
    assert response.status_code == 201

# ✅ Clean Architecture: тестируем логику отдельно (mock)
def test_register():
    mock_repository = MagicMock()
    service = UserService(repository=mock_repository)
    
    user = service.register(email="test@example.com", password="password123")
    
    assert user.email == "test@example.com"
    assert user.password_hash is not None
    # Быстро, не нужна БД!

3. Несвязность слоёв (Coupling)

Проблема: Изменение в БД требует изменения в контроллере:

# ❌ Слои тесно связаны
class UserController:
    def register(self, request):
        # Знает о Flask и Request
        user_data = request.json
        
        # Знает о SQLAlchemy
        user = User(email=user_data['email'])
        db.session.add(user)
        db.session.commit()
        
        # Знает о JSON ответе
        return {'id': user.id}

# Изменил ORM с SQLAlchemy на Tortoise? Переписываешь контроллер!

# ✅ Clean Architecture: слои независимы
# Контроллер не знает о БД
class RegisterUserRequest:
    email: str
    password: str

class UserService:
    def __init__(self, repository: UserRepository):
        self.repository = repository  # Абстракция!
    
    def register(self, request: RegisterUserRequest) -> User:
        # Не знает о Flask, SQLAlchemy, JSON
        user = User(email=request.email)
        return self.repository.save(user)

class UserController:
    def __init__(self, service: UserService):
        self.service = service
    
    def register(self, request):
        # Преобразует Flask Request в Domain объект
        user_request = RegisterUserRequest(**request.json)
        user = self.service.register(user_request)
        # Преобразует Domain объект в JSON
        return {'id': user.id}

4. Невозможность переиспользования логики

Проблема: Регистрация пользователя есть в API, но нельзя использовать в CLI, batch-обработке или другом фреймворке:

# ❌ Логика привязана к маршруту
@app.route('/register', methods=['POST'])
def register():
    # Вся логика здесь, нельзя переиспользовать
    ...

# ✅ Clean Architecture: логика отделена
class RegisterUserUseCase:
    def execute(self, email: str, password: str) -> User:
        # Используется везде: API, CLI, batch
        ...

# API
@app.route('/register', methods=['POST'])
def register_api():
    return RegisterUserUseCase().execute(...)

# CLI
@click.command()
def register_cli():
    user = RegisterUserUseCase().execute(...)
    print(f"Created user {user.email}")

# Batch
for email in emails:
    RegisterUserUseCase().execute(email, ...)

5. Смешивание разных уровней ответственности

Проблема: Валидация, бизнес-логика, персистенция, форматирование ответа — всё в одной функции:

# ❌ Всё смешано
def process_order(order_data):
    # 1. Валидация (Presentation)
    if not order_data.get('items'):
        return {'error': 'No items'}, 400
    
    # 2. Расчёты (Business Logic)
    total = sum(item['price'] * item['qty'] for item in order_data['items'])
    tax = total * 0.1
    
    # 3. Проверка у поставщика (External Service)
    supplier_api = requests.get(...)
    
    # 4. Сохранение (Persistence)
    db.execute("INSERT INTO orders...")
    
    # 5. Форматирование ответа (Presentation)
    return {'status': 'created', 'total': total + tax}

# ПРОБЛЕМЫ:
# - 200 строк в одной функции
# - Сложно тестировать
# - Сложно переиспользовать
# - Если изменяется БД, нужно менять валидацию

# ✅ Clean Architecture: слои разделены
# Presentation Layer
class OrderController:
    def create_order(self, request):
        order_dto = OrderDTO(**request.json)
        order = OrderService().create(order_dto)
        return OrderPresenter.present(order)

# Domain Layer (бизнес-логика)
class Order:
    def calculate_total(self, items):
        subtotal = sum(item.price * item.qty for item in items)
        return subtotal + (subtotal * self.TAX_RATE)

# Application Layer (Use Cases)
class CreateOrderUseCase:
    def execute(self, order_dto):
        # Валидация
        if not order_dto.items:
            raise ValidationError("No items")
        
        # Логика
        order = Order(items=order_dto.items)
        total = order.calculate_total(order_dto.items)
        
        # Внешние сервисы
        supplier = SupplierService().check_availability(order_dto.items)
        
        # Сохранение
        saved_order = OrderRepository().save(order)
        
        return saved_order

# Infrastructure Layer
class OrderRepository:
    def save(self, order):
        # Работает с БД
        ...

Слои Clean Architecture

# 1. ENTERPRISE BUSINESS RULES (Domain)
# Сущности, которые не зависят ни от чего
class User:
    def __init__(self, email, password):
        self.email = email
        self.password_hash = self._hash_password(password)
    
    def _hash_password(self, password):
        return hashlib.sha256(password.encode()).hexdigest()

# 2. APPLICATION BUSINESS RULES (Use Cases)
# Правила приложения: когда использовать User?
class RegisterUserUseCase:
    def __init__(self, user_repository):
        self.repository = user_repository
    
    def execute(self, email, password):
        if self.repository.find_by_email(email):
            raise UserAlreadyExistsError()
        
        user = User(email, password)
        return self.repository.save(user)

# 3. INTERFACE ADAPTERS (Controllers, Presenters, Gateways)
# Адаптируют между слоями
class UserController:
    def register(self, request):
        try:
            user = RegisterUserUseCase(UserRepository()).execute(
                email=request.json['email'],
                password=request.json['password']
            )
            return {'id': user.id}, 201
        except UserAlreadyExistsError:
            return {'error': 'User exists'}, 409

# 4. FRAMEWORKS & DRIVERS (Flask, Django, SQLite, etc.)
# Самый нестабильный слой
from flask import Flask
app = Flask(__name__)

@app.route('/register', methods=['POST'])
def register():
    return UserController().register(request)

# ЗАВИСИМОСТИ: Domain -> Use Cases -> Adapters -> Frameworks
# Никогда в обратном направлении!

Реальный пример: Архитектура проекта

my_app/
├── domain/                          # Enterprise Business Rules
│   ├── entities/
│   │   ├── user.py                 # User сущность
│   │   └── order.py                # Order сущность
│   └── exceptions.py               # Исключения domain уровня
│
├── application/                    # Application Business Rules
│   ├── use_cases/
│   │   ├── register_user.py       # Use case
│   │   └── create_order.py        # Use case
│   ├── repositories/
│   │   ├── user_repository.py     # Abstract repository
│   │   └── order_repository.py    # Abstract repository
│   └── dto/
│       ├── user_dto.py            # Data Transfer Object
│       └── order_dto.py           # Data Transfer Object
│
├── infrastructure/                # Infrastructure (Frameworks)
│   ├── repositories/
│   │   ├── sqlite_user_repo.py    # Реализация для SQLite
│   │   └── postgres_user_repo.py  # Реализация для Postgres
│   ├── services/
│   │   └── email_service.py       # Email отправка
│   └── config.py                  # Конфигурация
│
├── presentation/                  # Presentation Layer
│   ├── api/
│   │   ├── controllers/
│   │   │   └── user_controller.py
│   │   └── routes.py
│   └── cli/
│       ├── commands/
│       │   └── register_command.py
│       └── cli.py
│
└── main.py                        # Entry point

Преимущества Clean Architecture

# ✓ Тестируемость
# Можешь тестировать логику без БД, сети, файловой системы
service = RegisterUserUseCase(MockRepository())
assert service.execute(...) raises UserAlreadyExistsError

# ✓ Независимость от фреймворков
# Переключился с Flask на FastAPI — код не изменился

# ✓ Масштабируемость
# Легко добавлять новые фичи без влияния на существующий код

# ✓ Простота поддержки
# Каждый слой имеет чёткую ответственность

# ✓ Переиспользуемость
# Один use case используется везде: API, CLI, batch, events

# ✓ Независимость от деталей реализации
# SQLite -> PostgreSQL: меняешь только infrastructure слой

Когда использовать Clean Architecture

# ✓ ИСПОЛЬЗУЙ если:
# - Долгосрочный проект (> 1 года разработки)
# - Большая команда (> 3 человек)
# - Требуется высокая тестируемость
# - Вероятны изменения в технологиях
# - Нужна чистая, масштабируемая архитектура

# ✗ НЕ ИСПОЛЬЗУЙ если:
# - Простой скрипт (< 500 строк)
# - Прототип/MVP (быстрый результат важнее)
# - Один человек разрабатывает
# - Требуется максимальная скорость разработки

Заключение

Чистая архитектура решает проблему создания масштабируемого, тестируемого и maintainable кода, который:

  • Не зависит от фреймворков
  • Легко переиспользуется
  • Просто тестируется
  • Имеет чёткие границы между слоями
  • Может развиваться без усложнения

Сила Clean Architecture в том, что она позволяет разработчикам и архитекторам принимать решения независимо друг от друга, что особенно критично в больших командах и долгосрочных проектах.