Слоистая архитектура ПО
Для учебной модели достаточно простой слоистой структуры. Она не требует углубления в DDD, Clean Architecture или конкретный backend-фреймворк, но помогает не смешивать ответственности.
Слой — это способ разделить программную модель по типам ответственности. В одном месте находятся классы, принимающие внешние действия, в другом — классы, координирующие сценарии, в третьем — предметная модель, в четвёртом — работа с технической инфраструктурой. Такое разделение нужно не ради формальности, а для управляемости зависимостей.
Типовые слои:
- presentation layer: взаимодействие с пользователем или внешним клиентом;
- application layer: сценарии приложения и координация use cases;
- domain/model layer: предметная модель и бизнес-правила;
- infrastructure/data access layer: хранение, внешние сервисы, файлы, сетевые интеграции.
Главное правило слоистой модели: класс должен знать только о тех слоях, от которых он действительно зависит по ответственности. Пользовательский интерфейс или контроллер может обращаться к application layer. Application layer может использовать предметную модель и интерфейсы репозиториев или шлюзов. Infrastructure layer реализует технические детали хранения и интеграции. При этом предметная модель не должна напрямую зависеть от базы данных, HTTP-клиента, файловой системы или UI.
Типовые роли классов:
- Controller принимает внешнее действие и передаёт его в application layer;
- ApplicationService реализует сценарий приложения и координирует работу объектов;
- DomainService содержит предметную операцию, если она не принадлежит одному объекту;
- Repository скрывает способ получения и сохранения объектов;
- DTO переносит данные через границу слоя или интерфейса;
- Mapper преобразует одну техническую форму данных в другую;
- Gateway или Adapter изолирует внешнюю систему;
- Entity или Model представляет предметный объект в технической модели.
Эти роли не являются обязательным набором для любой системы. Их нужно добавлять только тогда, когда они помогают реализовать выбранные сценарии и удержать зависимости между слоями.
Например, в сценарии Оформить заказ контроллер может принять команду от клиента и передать её в OrderApplicationService. Сервис приложения загружает нужные данные через OrderRepository, вызывает операции предметных объектов, проверяет правила и сохраняет результат. Если нужно обратиться к платёжной системе, application layer работает не напрямую с конкретным API, а через PaymentGateway или похожий интерфейс. Конкретный адаптер платёжной системы относится к infrastructure layer.
Такой пример показывает, что слои не равны папкам в проекте. Папки помогают организовать код, но архитектурный смысл появляется только тогда, когда зависимости между классами соответствуют выбранным ролям. Если контроллер сам проверяет бизнес-правила, формирует SQL-запросы и вызывает внешний сервис, слоистая архитектура фактически отсутствует, даже если файлы лежат в разных каталогах.
Типичные ошибки слоистой модели:
- помещать бизнес-правила в контроллеры или DTO;
- делать repository ответственным за сценарий целиком;
- заставлять domain/model layer знать о таблицах, JSON или HTTP;
- добавлять сервисы без реальной ответственности;
- проводить зависимости между пакетами в обе стороны;
- называть классы по слоям, но не менять их фактическое поведение.
Для UML это важно потому, что техническая диаграмма классов и диаграмма пакетов должны показывать не просто набор классов, а выбранное распределение ответственности. Если зависимости между слоями не видны, модель плохо объясняет реализацию сценария.