Domain-Driven Development with Clean Architecture
Вспомним что нам предлагает DDD:
- Domain (Шаблоны домена)
— TransactionScript (сценарий транзакции)
набор действий объединённый в единую транзакцию
— DomainModel (модель предметной области)
модели объединяющие в себе действия
— ServiceLayer (слой служб)
сервисы объединяющие в себе логические действия - Source (Шаблоны источника данных)
— TableDataGateway (шлюз таблицы данных)
работа с таблицами и строками, подходит к TransactionScript, DomainModel
— RowDataGateway (шлюз записи данных)
работа со строкой, подходит к TransactionScript, DomainModel, ServiceLayer
— DataMapper (преобразователь данных)
гибрид первых двух, подходит к DomainModel, ServiceLayer - Presentation (Шаблоны представления)
— ModelViewController (Модель-Вьюха-Контроллер)
— FrontController (контроллер запросов)
Контроллер обработки общего поведения для всех запросов
— ApplicationController (контроллер приложения)
Контроллер обработки запроса извлекающий данные для навигации и т.д.
— PageController (контроллер страниц)
Контроллер для страниц, у запроса свои контроллеры страниц обрабатывающие ввод
— TemplateView (шаблонизатор)
преобразование данных представляется по шаблону
— TransformView (трансформатор)
преобразование данных представляется с трансформацией
— TwoStepView (преобразователь)
гибрид преобразований — трансформация с последующей шаблонизацией
Теперь вспомним что нам предлагает Clean Architecture:
- Тестируемая, не зависимая от UI, БД, фреймворков и библиотек архитектура
- Более верхние слои ничего не знают о существовании более нижних
- Более нижние слои используют более верхние внедряя их через интерфейсы
Учитывая всё это, попробуем сделать сплит этих вариантов:
Framework | Application | Domain | Source |
FrontController ApplicationControllerDrivers Database Services … |
PageController — Run UseCase — Send DomainEvents TemplateView TransformView TwoStepView |
UseCase — TransactionScript — ServiceLayer — DomainModel — .. GoFCollection Entity |
TableDataGateway RowDataGateway DataMapper |
Слой фреймворка/библиотек, слой отвечает за стабильную работу приложения и предоставляет ему необходимые библиотеки | Слой приложения, слой отвечает за запуск необходимого варианта использования и возвращает его результат в необходимом виде | Слой обработки данных (слой домена), слой отвечает за работу вариантов использования (UseCase) | Слой доступа к данным, слой отвечает за работу с хранилищами данных |
Невозможно не заметить, что у нас появились четыре ярко выделенных блока. Связь между ними организуется с помощью «портов» (интерфейсов) и «адаптеров» (реализаций).
Для примера, сценарий «пользователь выбрал службу доставки которая доставляет грузы до специально организованных точек выдачи и город доставки, необходимо отобразить ему список этих точек в выбранном городе». Реализуем:
- В слое фреймворка подгрузим необходимый контроллер
- В слое приложения найдём сценарий «получение точек самовывоза» для «службы доставки», и передадим ему выбранный город и службу
- В слое домена мы находим необходимый маппер для этого сервиса, реализующий фасад для апи, и передадим ему выбранный город
- В слое данных мы получим доступ к адаптеру этой «службы доставки» и запросим у него список точек
- Вернувшись в слой домена мы сделаем полученные точки коллекцией сущностей
- Вернувшись в слой приложения мы преобразуем эту коллекцию к необходимому виду
- Вернувшись в слой фреймворка передадим ему управление, для завершения запроса
Данный вариант легко поддаётся тестированию на всех описанных шагах, так как архитектура уберегает нас от жёстких связей с помощью портов стоящих на границах слоёв и отделяет всю бизнес-логику в отдельный самодостаточный слой.
Теперь у нас есть «варианты использования» системы, возможно детализированные до конкретных шагов, и выбранная архитектура бизнес-логики. Теперь осталось дело за малым, перенести варианты использования в выбранную архитектуру и покрыть её тестами.