Слои в чистой архитектуре
Слои в чистой архитектуре — это фундаментальное понятие, определяющее структуру программного обеспечения таким образом, чтобы бизнес-логика оставалась независимой от внешних факторов: баз данных, пользовательского интерфейса, фреймворков и инфраструктуры. Чистая архитектура, предложенная Робертом Мартином (Uncle Bob), разделяет приложение на строго организованные слои, где зависимости направлены только внутрь — от внешних к внутренним компонентам. Это позволяет легко тестировать, масштабировать и поддерживать код.
- Основные принципы чистой архитектуры
- Обзор слоёв в чистой архитектуре
- Слой домена: сердце бизнес-логики
- Что должно быть в доменном слое
- Слой приложения: оркестр взаимодействий
- Типичные компоненты слоя приложения
- Слой интерфейсов: мост к внешнему миру
- Инфраструктурный слой: реализация деталей
- Что размещать в инфраструктуре
- Правила зависимостей и инверсия управления
- Типичные ошибки и как их избежать
- Экспертное мнение
- Анна Ковалёва, старший архитектор, банк «Точка»
- Вопросы и ответы
- Заключение
Основные принципы чистой архитектуры
Чистая архитектура — это не набор конкретных технологий, а концепция проектирования, ориентированная на долгосрочную поддерживаемость и гибкость. Её главная цель — отделить то, что меняется редко (бизнес-правила), от того, что изменяется часто (интерфейсы, базы данных, внешние API). Архитектура строится вокруг концентрических кругов, где каждый внутренний уровень более абстрактный и важный, чем внешний.
Центральным элементом является слой домена, содержащий сущности и бизнес-правила. Он полностью независим и не должен зависеть ни от одного другого слоя. Внешние слои — приложения, интерфейсов и инфраструктуры — служат для реализации деталей и должны адаптироваться к требованиям ядра.
Подход позволяет быстро реагировать на изменения требований. Например, если нужно заменить веб-интерфейс на мобильное приложение или сменить базу данных с PostgreSQL на MongoDB — это не затрагивает бизнес-логику. Такая модульность особенно ценна в крупных проектах с длительным жизненным циклом.
Обзор слоёв в чистой архитектуре
Стандартная модель чистой архитектуры включает четыре основных слоя, расположенных по уровням абстракции:
- Доменный слой — содержит бизнес-сущности, правила и логику.
- Слой приложения — координирует действия между слоями, реализует use cases.
- Слой интерфейсов — отвечает за взаимодействие с пользователем: API, UI, CLI.
- Инфраструктурный слой — реализует технические детали: работа с БД, сетевые вызовы, файловая система.
Каждый слой имеет строго определённую зону ответственности. Переход между слоями происходит через чётко заданные интерфейсы, а зависимости всегда направлены внутрь — от внешнего к внутреннему. Это правило называется Принципом единственной зависимости.
Например, контроллер в слое интерфейсов может вызывать сервис из слоя приложения, но не наоборот. Инфраструктурный слой реализует интерфейсы, объявленные во внутренних слоях, что позволяет использовать паттерн Dependency Inversion.
Слой |
Ответственность |
Зависимости |
|---|---|---|
Домен |
Бизнес-сущности, правила, события |
Никаких |
Приложение |
Use cases, сценарии, транзакции |
Только от домена |
Интерфейс |
API, UI, сообщения |
От приложения и инфраструктуры |
Инфраструктура |
Реализация хранилищ, внешних сервисов |
От всех внутренних слоев (через интерфейсы) |
Слой домена: сердце бизнес-логики
Доменный слой — это ядро системы. Здесь сосредоточены сущности (entities), объекты значения (value objects), доменные сервисы и события. Именно этот слой определяет, «что делает система» с точки зрения бизнеса. Он должен быть максимально изолирован и не содержать упоминаний о базах данных, веб-фреймворках или HTTP.
Сущности — это объекты с идентичностью, поведением и состоянием. Например, в системе интернет-магазина сущностью может быть `Order`, который имеет ID, список товаров, статус и методы вроде `confirm()` или `cancel()`. Все эти методы реализуют бизнес-правила: например, заказ нельзя отменить после отправки.
Доменные события — мощный механизм реактивности. Когда происходит важное действие (например, «Заказ подтверждён»), генерируется событие, которое может быть обработано другими частями системы. Это позволяет строить слабосвязные архитектуры, особенно в микросервисах.
Что должно быть в доменном слое
- Сущности с бизнес-поведением
- Объекты значений (например, Email, Money)
- Интерфейсы репозиториев (без реализации!)
- Доменные события и обработчики
- Фабрики и спецификации (при использовании DDD)
Слой приложения: оркестр взаимодействий
Слой приложения отвечает за выполнение сценариев использования (use cases). Он не содержит бизнес-логики, но знает, какие сущности и сервисы нужно задействовать, в каком порядке и при каких условиях. По сути, это «сценарный режиссёр», управляющий потоком данных между доменом и внешним миром.
Например, use case «Оформление заказа» может включать:
- Проверку наличия товара
- Создание сущности Order
- Вызов платежного шлюза
- Сохранение заказа в репозиторий
- Отправку уведомления
Команды и запросы — популярный подход в этом слое. Используется паттерн CQS (Command Query Separation): команды изменяют состояние, запросы его читают. Это упрощает тестирование и кэширование.
Типичные компоненты слоя приложения
- Use case-сервисы (например, CreateOrderUseCase)
- DTO (Data Transfer Objects) для передачи данных
- Интерфейсы шлюзов (gateways) для внешних вызовов
- Транзакционные менеджеры
Слой интерфейсов: мост к внешнему миру
Интерфейсный слой — это точка входа для всех внешних взаимодействий. Он включает веб-API (REST, GraphQL), пользовательский интерфейс (веб, мобильное приложение), CLI или сообщения из очередей (Kafka, RabbitMQ). Его задача — принять запрос, преобразовать его в формат, понятный слою приложения, и вернуть результат.
Контроллеры, эндпоинты, хендлеры событий — всё это части этого слоя. Они должны быть «тонкими»: минимум логики, максимум делегирования. Например, REST-контроллер получает JSON, валидирует его, создаёт DTO и передаёт в use case.
Преобразование данных — ключевая задача. Внешний формат (например, JSON) часто отличается от внутреннего. Здесь используются мапперы, валидаторы и адаптеры. Но важно: валидация формата — на этом уровне, валидация бизнес-правил — только в домене.
Инфраструктурный слой: реализация деталей
Инфраструктурный слой отвечает за реализацию технических аспектов: работа с базой данных, отправка email, вызов внешних API, кэширование. Он реализует интерфейсы, объявленные во внутренних слоях (например, `IOrderRepository`), но никогда не определяет их.
Например, доменный слой объявляет интерфейс `NotificationService`, а инфраструктурный предоставляет реализацию `EmailNotificationService` или `PushNotificationService`. Это позволяет легко переключаться между реализациями без изменения бизнес-логики.
Именно здесь подключаются ORM (например, Hibernate, TypeORM), HTTP-клиенты, очереди сообщений. Однако важно не «засорять» ядро знанием об этих технологиях. ORM-сущности должны быть в инфраструктуре, а не в домене.
Что размещать в инфраструктуре
- Реализации репозиториев (через ORM или SQL)
- Клиенты внешних API
- Сервисы уведомлений, почты, SMS
- Конфигурация, DI-контейнеры
- Адаптеры для очередей и событий
Правила зависимостей и инверсия управления
Главное правило чистой архитектуры: зависимости направлены только внутрь. Внешние слои могут зависеть от внутренних, но не наоборот. Это достигается через инверсию зависимостей (Dependency Inversion Principle, DIP).
Например, домен объявляет интерфейс `IUserRepository`, а инфраструктура предоставляет реализацию `PostgresUserRepository`. При создании приложения используется DI-контейнер, который внедряет нужную реализацию.
Без инверсии зависимостей архитектура становится жёсткой. Если домен зависит от `PostgresUserRepository`, замена базы данных потребует переписывания ядра — что недопустимо.
Правило |
Пример соблюдения |
Пример нарушения |
|---|---|---|
Зависимости внутрь |
Контроллер → UseCase → Entity |
Entity → DatabaseContext |
Инверсия зависимостей |
UseCase зависит от IRepo, реализация в инфраструктуре |
UseCase создаёт PostgresRepo напрямую |
Независимость домена |
Доменный слой компилируется отдельно |
Импорт flask или spring в сущности |
Типичные ошибки и как их избежать
Несмотря на простоту концепции, на практике часто допускаются критические ошибки:
- Логика в контроллерах — бизнес-правила попадают в слой интерфейсов. Решение: выносить всё, что не связано с HTTP, в use cases или домен.
- ORM-сущности как доменные — использование JPA/Hibernate-аннотаций в сущностях. Решение: держать ORM в инфраструктуре, маппить данные при переходе слоёв.
- Жёсткие зависимости — прямое создание объектов вместо внедрения через интерфейсы. Решение: использовать DI и фабрики.
- Игнорирование DIP — внутренние слои зависят от внешних. Решение: рефакторинг через выделение интерфейсов.
Еще одна частая проблема — избыточная сложность. Не во всех проектах нужна полная чистая архитектура. Для простых CRUD-приложений она может быть избыточной. Оценивайте масштаб и срок жизни проекта.
Экспертное мнение
Анна Ковалёва, старший архитектор, банк «Точка»
Ключевой момент — дисциплина. Команда должна строго следовать правилам. Мы ввели code review с акцентом на архитектурные нарушения. Также используем статический анализ (например, ArchUnit), чтобы автоматически ловить попытки импорта инфраструктуры в домен.
Сейчас мы можем запускать один и тот же бизнес-движок в веб-приложении, микросервисе и batch-обработчике — просто меняя обертку. Это дало нам гибкость, которую раньше можно было только мечтать.»
Вопросы и ответы
Заключение
Чистая архитектура — это не модный тренд, а проверенный временем подход к созданию долгоживущего, гибкого и поддерживаемого ПО. Разделение на слои с чёткими правилами зависимостей позволяет изолировать бизнес-ценность от технических деталей, что критически важно в условиях постоянных изменений.
- Соблюдайте правило «зависимости внутрь»