Python чистая архитектура
Python чистая архитектура — это подход к проектированию программного обеспечения, при котором код организован таким образом, чтобы быть независимым от внешних деталей: баз данных, веб-фреймворков, пользовательских интерфейсов и механизмов доставки. Основная цель — создать гибкую, тестируемую и поддерживаемую систему, где бизнес-логика отделена от технической инфраструктуры. Такой подход позволяет легко модифицировать компоненты без риска сломать всю систему.
- Что такое чистая архитектура и зачем она нужна
- Основные принципы чистой архитектуры в Python
- Как работает инверсия зависимостей
- Структура слоёв: как организовать проект по правилам чистой архитектуры
- Пример структуры каталогов
- Инверсия зависимостей и внедрение через интерфейсы
- Преимущества внедрения зависимостей
- Практическая реализация: пример на Flask и SQLAlchemy
- Шаг 1: Определяем сущность
- Шаг 2: Создаём интерфейс репозитория
- Шаг 3: Реализуем бизнес-логику
- Шаг 4: Адаптер для SQLAlchemy
- Шаг 5: Контроллер Flask
- Типичные ошибки и как их избежать
- Чек-лист проверки архитектуры
- Экспертное мнение
- Анна Волкова, Lead Backend Developer, SberDevices
- Вопросы и ответы
- Заключение
Что такое чистая архитектура и зачем она нужна
Чистая архитектура (Clean Architecture) была предложена Робертом Мартином (Uncle Bob) как универсальный способ организации кода, при котором система строится вокруг своей основной бизнес-логики. В центре находится «ядро» — доменная модель, которая не зависит ни от чего внешнего. Чем дальше от центра, тем больше технических деталей: фреймворки, базы данных, API-интерфейсы.
Такая структура особенно важна в Python-проектах, где динамическая типизация и гибкость языка могут привести к «спагетти-коду», если не соблюдать дисциплину проектирования. Без четкой архитектуры даже средний по масштабу сервис становится трудно поддерживать, тестировать и развивать.
Преимущества чистой архитектуры очевидны: упрощается написание юнит-тестов, так как ядро не требует запуска базы данных или сервера; повышается переиспользуемость кода; снижается стоимость изменений. Например, можно заменить Flask на FastAPI или SQLite на PostgreSQL без переписывания бизнес-логики.
Основные принципы чистой архитектуры в Python
Чистая архитектура опирается на несколько ключевых принципов ООП и SOLID. Они становятся основой для построения устойчивых систем.
Первый — принцип единственной ответственности (SRP). Каждый класс или функция должна решать одну задачу. Например, класс `OrderProcessor` занимается только обработкой заказов, а не отправкой email или записью в базу.
Второй — открытость/закрытость (OCP). Код должен быть открыт для расширения, но закрыт для модификации. Это значит, что новые функции добавляются через наследование или композицию, а не правкой существующих файлов.
Третий — инверсия зависимостей (DIP). Высокоуровневые модули не должны зависеть от низкоуровневых. Оба должны зависеть от абстракций. В Python это реализуется через протоколы, ABC (Abstract Base Classes) или интерфейсы-заглушки.
Как работает инверсия зависимостей
Представьте, что у вас есть сервис, который обрабатывает платежи. Вместо того чтобы напрямую использовать `StripePaymentGateway`, вы создаёте интерфейс `PaymentGateway`, от которого наследуются все провайдеры.
«`python
from abc import ABC, abstractmethod
class PaymentGateway(ABC):
@abstractmethod
def charge(self, amount: float) -> bool:
pass
«`
Теперь ваш `PaymentService` зависит от абстракции, а не от конкретной реализации.
Структура слоёв: как организовать проект по правилам чистой архитектуры
Классическая чистая архитектура делит приложение на четыре слоя:
- Entities — бизнес-сущности и правила домена;
- Use Cases — сценарии использования, оркестрируют взаимодействие сущностей;
- Interface Adapters — адаптеры между внутренним миром и внешним (например, сериализаторы, контроллеры);
- Frameworks & Drivers — фреймворки, базы данных, веб-серверы.
В Python это можно представить как директории в проекте:
Слой |
Путь в проекте |
Ответственность |
|---|---|---|
Entities |
/domain/models.py |
Определение сущностей: User, Order, Product |
Use Cases |
/application/services.py |
Логика: create_order, process_payment |
Interface Adapters |
/adapters/controllers.py |
HTTP-обработчики, сериализация |
Frameworks & Drivers |
/infrastructure/db.py |
Работа с БД, внешние API |
Главное правило: зависимость всегда направлена внутрь. Внешние слои могут импортировать внутренние, но не наоборот. Например, `controllers.py` может использовать `services.py`, но `services.py` не должен знать о контроллерах.
Пример структуры каталогов
my_project/ ├── domain/ │ ├── models.py │ └── exceptions.py ├── application/ │ ├── services.py │ └── use_cases.py ├── adapters/ │ ├── controllers.py │ └── serializers.py ├── infrastructure/ │ ├── database/ │ │ ├── repositories.py │ │ └── models.py │ └── external/ │ └── payment_gateway.py └── main.py
Такая организация помогает команде быстро находить нужный код и снижает риск конфликтов при слиянии веток.
Инверсия зависимостей и внедрение через интерфейсы
Один из самых сложных, но важных аспектов — управление зависимостями. В Python нет встроенной DI-системы, как в Spring (Java), но есть несколько способов реализовать внедрение.
Первый способ — ручное внедрение. Вы передаёте зависимости в конструктор:
«`python
class OrderService:
def __init__(self, payment_gateway: PaymentGateway, order_repo: OrderRepository):
self.payment_gateway = payment_gateway
self.order_repo = order_repo
«`
Это просто, тестируемо, но требует ручной сборки объектов.
Второй способ — использовать библиотеки, такие как `dependencies` (от python-dependency-injector) или `Injector`. Они позволяют декларативно описывать зависимости.
«`python
from dependencies import container
container.register(‘payment_gateway’, StripeGateway)
container.register(‘order_service’, OrderService)
«`
Третий способ — фабрики. Вы создаёте фабричные функции, которые собирают готовые сервисы.
«`python
def make_order_service() -> OrderService:
db = get_db()
repo = SQLAlchemyOrderRepository(db)
gateway = StripePaymentGateway(api_key=»…»)
return OrderService(repo, gateway)
«`
Преимущества внедрения зависимостей
- Упрощается тестирование: можно подставить мок вместо реального шлюза.
- Повышается гибкость: легко менять реализации (например, перейти с Stripe на PayPal).
- Снижается связанность: компоненты не жёстко привязаны друг к другу.
Практическая реализация: пример на Flask и SQLAlchemy
Рассмотрим мини-приложение для оформления заказа. Мы построим его по принципам чистой архитектуры.
Шаг 1: Определяем сущность
«`python
# domain/models.py
class Order:
def __init__(self, user_id: int, amount: float):
self.user_id = user_id
self.amount = amount
self.status = «created»
«`
Шаг 2: Создаём интерфейс репозитория
«`python
# domain/repositories.py
from abc import ABC, abstractmethod
from .models import Order
class OrderRepository(ABC):
@abstractmethod
def save(self, order: Order) -> None:
pass
«`
Шаг 3: Реализуем бизнес-логику
«`python
# application/services.py
from domain.models import Order
from domain.repositories import OrderRepository
class OrderService:
def __init__(self, repo: OrderRepository):
self.repo = repo
def create_order(self, user_id: int, amount: float) -> Order:
if amount <= 0:
raise ValueError("Сумма должна быть положительной")
order = Order(user_id, amount)
self.repo.save(order)
return order
«`
Шаг 4: Адаптер для SQLAlchemy
«`python
# infrastructure/database/repositories.py
from sqlalchemy.orm import Session
from domain.repositories import OrderRepository
from domain.models import Order
class SQLAlchemyOrderRepository(OrderRepository):
def __init__(self, session: Session):
self.session = session
def save(self, order: Order) -> None:
# Предположим, есть ORM-модель OrderModel
db_order = OrderModel(user_id=order.user_id, amount=order.amount)
self.session.add(db_order)
self.session.commit()
«`
Шаг 5: Контроллер Flask
«`python
# adapters/controllers.py
from flask import request, jsonify
from application.services import OrderService
def create_order_controller(service: OrderService):
data = request.json
try:
order = service.create_order(data[‘user_id’], data[‘amount’])
return jsonify({«status»: «success», «order_id»: order.id}), 201
except Exception as e:
return jsonify({«error»: str(e)}), 400
«`
Теперь всё соединяется в `main.py`:
«`python
# main.py
from flask import Flask
from infrastructure.database.repositories import SQLAlchemyOrderRepository
from application.services import OrderService
from adapters.controllers import create_order_controller
app = Flask(__name__)
# Сборка зависимостей
db_session = get_db_session()
repo = SQLAlchemyOrderRepository(db_session)
service = OrderService(repo)
@app.route(‘/order’, methods=[‘POST’])
def create_order():
return create_order_controller(service)
«`
Типичные ошибки и как их избежать
Многие разработчики начинают с чистой архитектуры, но допускают ошибки, которые сводят пользу к нулю.
- Нарушение направления зависимостей. Самая частая ошибка — импорт из внешних слоёв в ядро. Например, использование `Flask.request` внутри сервиса. Это делает код нечитаемым и не тестируемым.
- Избыточная абстракция. Не нужно создавать интерфейс для каждой мелкой функции. Абстрагируйте только те части, которые реально могут меняться (например, способы оплаты).
- Игнорирование исключений домена. Бизнес-правила должны выбрасывать доменные исключения, а не HTTP-ошибки. Конвертацию делайте на уровне адаптера.
- Слишком сложная DI-система. Иногда проще передавать зависимости вручную, чем настраивать контейнер с десятью зависимостями.
Чек-лист проверки архитектуры
- Ядро не импортирует ничего из `infrastructure` или `adapters`?
- Юнит-тесты бизнес-логики запускаются без базы данных?
- Можно ли заменить фреймворк (Flask → FastAPI) без правки сервисов?
- Все внешние вызовы (БД, API) инкапсулированы в адаптерах?
- Зависимости передаются через конструктор или интерфейсы?
Экспертное мнение
Анна Волкова, Lead Backend Developer, SberDevices
«Мы внедрили чистую архитектуру в микросервисе управления устройствами. До этого каждый релиз сопровождался страхом сломать что-то в логике начисления подписок. После рефакторинга мы получили:
- Снижение времени на добавление нового провайдера оплаты с 3 дней до 4 часов;
- Рост покрытия юнит-тестами с 40% до 85%;
- Возможность запускать бизнес-логику вне контекста Django.
Ключевой шаг — обучение команды. Мы провели внутренние воркшопы по SOLID и DDD. Сейчас любой стажёр понимает, куда класть новый класс.»
Вопросы и ответы
Заключение
Чистая архитектура в Python — это не модный тренд, а проверенный способ строить долгоживущие, масштабируемые приложения. Она особенно ценна в условиях быстрых изменений требований и необходимости постоянного рефакторинга. Разделив бизнес-логику от инфраструктуры, вы получаете систему, которую легко тестировать, развивать и передавать новым разработчикам.
- Ядро приложения должно быть свободно от внешних зависимостей.
- Зависимости всегда направлены внутрь — от внешних слоёв к центру.
- Используйте абстракции для замены внешних компонентов (БД, API).
- Тестируйте бизнес-логику без запуска сервера и базы данных.
- Внедрение зависимостей — ключ к гибкости и тестируемости.
⚠️ Дисклеймер — нажмите, чтобы развернуть
Материалы, опубликованные в разделе «Блог» на сайте RU DESIGN SHOP (rudesignshop.ru), носят исключительно информационный и ознакомительный характер и не являются руководством к действию, финансовой рекомендацией, медицинской услугой, ветеринарным назначением либо рекламой товаров и услуг, включая азартные игры. Публикации не содержат призывов к участию в азартных играх и не направлены на продвижение соответствующих операторов.
Безопасность применения товаров и веществ: при использовании строительных материалов, бытовой химии, пестицидов и агрохимикатов необходимо строго следовать инструкциям производителя и действующему законодательству Российской Федерации, включая Федеральный закон РФ от 19.07.1997 № 109-ФЗ «О безопасном обращении с пестицидами и агрохимикатами».
Упоминание товарных знаков, брендов и организаций носит исключительно информационный характер и не означает наличие партнёрских отношений или одобрения со стороны правообладателей.
Материалы, содержащие сведения о медицинских, ветеринарных или косметических средствах, представлены в справочных целях и не являются медицинской консультацией или назначением. Перед применением рекомендуется обратиться к врачу, ветеринарному специалисту или иному сертифицированному профессионалу.
Возрастные ограничения: материалы, содержащие сведения о продукции категории 18+, включая алкоголь или азартные игры, предназначены исключительно для совершеннолетней аудитории и публикуются в информационных целях.
Правовая ответственность: решения, принятые на основе опубликованной информации, пользователь принимает самостоятельно и на свой риск; редакция и авторы несут ответственность в пределах, установленных законодательством Российской Федерации.
Редакция не допускает публикаций, содержащих пропаганду экстремизма, терроризма, наркотических средств или суицида; подобные материалы подлежат немедленному удалению.
Упоминание организаций с ограниченным статусом: компания Meta Platforms Inc. (социальные сети Facebook и Instagram) признана экстремистской организацией решением суда РФ, её деятельность запрещена на территории Российской Федерации; любые упоминания приводятся исключительно в информационных целях.
Авторские права и источники: информация собирается из открытых источников; её актуальность указывается на дату публикации и может изменяться.
Изображения и иллюстрации используются на условиях, разрешённых правообладателями. При возникновении претензий редакция готова оперативно рассмотреть обращение и внести необходимые изменения.
Персональные данные и cookies: сайт использует cookies и обрабатывает персональные данные пользователей в соответствии с Федеральным законом № 152-ФЗ «О персональных данных» и Политикой конфиденциальности RU DESIGN SHOP.
Мнения авторов могут не совпадать с позицией государственных органов или коммерческих организаций, упомянутых в материалах.