Какую проблему решает чистая архитектура?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Какую проблему решает чистая архитектура
Чистая архитектура (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 в том, что она позволяет разработчикам и архитекторам принимать решения независимо друг от друга, что особенно критично в больших командах и долгосрочных проектах.