Пример реализации проекта в архитектуре CQRS, очень сильно сокращенная версия Reference application Modular monolith with DDD
Применяется в случае, если планируется сделать MVP, который с большой долей вероятности будет потом распилен на микросервисы
Проект состоит из 3-х слоев
- API - слой, содержащий контроллеры
- Application - слой, содержащий основную бизнес-логику. Ближайший аналог Use Cases в Clean Architecture
- Database - слой данных
Основная логика содержится в Application слое. Слои API и Database зависят от слоя Application и являются относительно него сервисными (т.е. обслуживают его потребности). Каждый слой данных использует свои объекты/модели преобразуемые друг в друга либо маппером (в простом случае) или кодом (в случае если структура объектов сильно различается между слоями).
При развитии приложения реализация слоев API и Database могут быть заменены на другие (например, API может быть заменен на GRPC, Database может быть заменен на Redis полностью или частично и т.д.), при этом слой Application не изменится.
Взаимодействие между слоями происходит через Commands и Query с использованием библиотеки MediatR.
Существуют 2 типа команд: ICommand (IQuery) - для взаимодействия между API и Application слоем и IDBCommand (IDBQuery) для взаимодействия между Application и Database. Запросы (I[DB]Querу) не должны изменять данные, команды (I[DB]Command) могут изменять данные.
Для того, чтобы при разрастании проекта не возникало ситуации, когда есть куча команд/запросов, которые 10 лет назад использовались, но сейчас уже устарели и не используются все необходимое для выполнения UseCase храниться в одном каталоге.
Например, Category содержит GetAllUseCase в котором есть GetAllCategoriesQuery, GetAllCategoriesQueryHandler (основная логика, в этом случае вырожденная), GetAllCategoriesDBQuery. В случае, если нам по каким-то причинам не нужен будет этот UseCase мы можем удалить целиком этот каталог и все зависимости от него (компилятор скажет какие).
В слое Application наименования должны отображать бизнес-сущности или UseCase. Если возвращается список чего-то, наименование должно оканчиваться на Item (например CategoryItem).
Допустимо использовать объекты в стиле DDD (см. пример Category) в случаях если производятся операции в стиле CRUD, но в большинстве UseCases скорее всего будут использоваться специфические объекты (например изменение только наименования у категории).
В слое API модели, используемые в контроллерах для входных/выходных параметров именуются:
-
<Что-то понятное>Request для входных моделей
-
<Что-то понятное>Response для выходных моделей
В слое Database объекты именуются в традиционном стиле в единственном числе, наименование таблиц настроено с применением UseSnakeCaseNamingConvention
- Ответить на вопрос есть ли понимание зачем так извращаться, возможно предложить варианты улучшения (в т.ч. по коду)
- Предложить какие тесты можно написать в данном примере
- Написать (можно без реализации Database слоя) команду добавления нового продукта. У каждого продукта должны быть обязательно заполнены значения всех параметров категории, к которой относится продукт. Не должно быть значений параметров, которых нет в категории.
{
"id": "a427f99e-d620-49fb-afa3-69b81b41556c",
"name": "Super toy",
"description": "Supper pupper toys",
"attributes": [
{
"id": "c732a300-3dd9-46b6-8d38-0b6512212fa1",
"name": "Height",
"type": "Int"
},
{
"id": "DA2DAB5C-34DC-4C58-A2C9-2E0B6F67FB6D",
"name": "Width",
"type": "Int"
}
]
}