Команды и запросы CQRS - принадлежат ли они к домену?

В CQRS выполняются ли команды и запросы в домене?

Имеются ли события в домене?

Если это так, то обработчики команд/запросов просто реализуются в инфраструктуре?

Прямо сейчас у меня это сложилось вот так:

Application.Common
Application.Domain
  - Model
    - Aggregate
  - Commands
  - Queries
Application.Infrastructure
  - Command/Query Handlers
  - ...
Application.WebApi
  - Controllers that utilize Commands and Queries

Другой вопрос, откуда вы поднимаете события? Обработчик команд или агрегирование домена?

Ответы

Ответ 1

Команды и События могут иметь разные проблемы. Они могут представлять собой технические проблемы, проблемы интеграции, проблемы с доменом...

Я предполагаю, что если вы спросите о домене, вы реализуете модель домена (возможно, даже с помощью Driven Design).

Если это так, я попытаюсь дать вам очень упрощенный ответ, так что вы можете иметь отправную точку:

  • Команда: бизнес-намерение, то, что вы хотите, чтобы система сделала. Сохраните определение команд в домене. Технически это просто чистый DTO. Имя команды всегда должно быть обязательным "PlaceOrder", "ApplyDiscount". Одна команда обрабатывается только одним обработчиком команд и может быть отброшена, если она недействительна (однако вы должны сделать все возможное подтверждение перед отправкой команды в ваш домен, чтобы он не может потерпеть неудачу)
  • Событие: это то, что произошло в прошлом. Для бизнеса это неизменный факт, который нельзя изменить. Сохраняйте определение события домена в домене. Технически это также объект DTO. Однако название события всегда должно быть в прошлом "OrderPlaced", "DiscountApplied". Обычно событиями являются pub/sub. Один издатель много обработчиков.

Если это так, то обработчики команд/запросов просто реализуются в инфраструктуре?

Обработчики команд семантически похожи на уровень сервиса приложения. Обычно уровень обслуживания приложений отвечает за организацию домена. Он часто строится вокруг случаев использования бизнеса, например, "Размещение заказа". В этих случаях использования вызывают бизнес-логику (которая всегда должна быть инкапсулирована в домене) через совокупные корни, запросы и т.д. Это также хорошее место для обработки проблем с перекрестными ссылками транзакции, проверка, безопасность и т.д.

Однако прикладной уровень не является обязательным. Это зависит от функциональных и технических требований и выбора архитектуры, которая была сделана. Ваша укладка кажется правильной. Я бы лучше сохранил обработчики команд на границе системы. Если не существует надлежащего уровня приложения, обработчик команд может играть роль оркестра. Если вы разместите его в Домене, вы не сможете легко справляться с проблемами перекрестных ссылок. Это компромисс. Вы должны знать о преимуществах и недостатках вашего решения. Он может работать в одном случае, а не в другом.

Что касается обработчиков событий. Я обрабатываю его вообще в

  • Уровень приложения, если событие инициирует модификацию другого агрегата в том же ограниченном контексте или если событие вызывает службу инфраструктуры.
  • Уровень инфраструктуры, если событие необходимо разделить на несколько потребителей или интегрировать другой ограниченный контекст.

В любом случае вы не должны слепо следовать правилам. Всегда есть компромиссы и различные подходы.

Другой вопрос, откуда вы поднимаете события? Обработчик команд или агрегирование домена?

Я делаю это из корневого агрегата домена. Потому что домен отвечает за повышение событий. Поскольку всегда существует техническое правило, что вы не должны публиковать события, если была проблема с сохранением изменений в совокупности, и наоборот, я использовал подход, используемый в Event Sourcing, и это прагматично. Мой общий корень имеет коллекцию событий Unpublished. В реализации моего репозитория я проверил коллекцию событий Unpublished и передал их промежуточному программному обеспечению, ответственному за публикацию событий. Легко контролировать, что если существует исключение, сохраняющее общий корень, события не публикуются. Некоторые говорят, что это не ответственность репозитория, и я согласен, но кто заботится. Какой выбор. Имея неудобный код для публикации событий, который проникает в ваш домен со всеми проблемами инфраструктуры (транзакция, обработка исключений и т.д.) Или является прагматичным и обрабатывает все на уровне инфраструктуры? Я сделал и то и другое, и поверьте, я предпочитаю быть прагматичным.

Подводя итог, нет единого способа делать вещи. Всегда знайте свои бизнес-потребности и технические требования (масштабируемость, производительность и т.д.). Тогда сделайте свой выбор на основе этого. Я описываю, что я обычно делал в большинстве случаев, и это сработало. Это просто мое мнение.

Ответ 2

В некоторых реализациях, Команды и обработчики находятся на уровне приложения. In другие, они принадлежат к домену. Я часто видел первое в OO-системах, а последнее больше в функциональных реализациях, что тоже то, что я делаю сам, но YMMV.

Если по событиям вы имеете в виду события домена, ну... да, я рекомендую их определить на уровне домена и испустить их из объектов домена. События домена являются неотъемлемой частью вашего вездесущего языка и даже будут непосредственно придуманными экспертами домена, если вы, например, практикуете Event Storming, поэтому он определенно делает смысл поставить их там.

То, что я думаю, вы должны иметь в виду, но это то, что нет правила относительно большинства этих технических деталей, которые заслуживают того, чтобы быть установленными в камне. Бесчисленное множество вопросов о проектах шаблонов DDD и расслоении и "топографии" на SO, но, откровенно говоря, я не думаю, что эти проблемы имеют решающее значение для создания надежного, эффективного и поддерживаемого приложения, особенно потому, что они настолько зависимы от контекста. Вы, скорее всего, не будете организовывать код для торговой системы с миллионами совокупных изменений в минуту так же, как если бы вы использовали платформу для публикации блога на 50 человек, даже если оба они разработаны с использованием DDD-подхода. Иногда вам приходится пробовать вещи для себя на основе вашего контекста и учиться на этом пути.

Ответ 3

Команда и события - это DTO. Вы можете иметь обработчики команд и запросы в любом слое/компоненте. Событие - это просто уведомление о том, что что-то изменилось. Вы можете иметь все типы событий: домен, приложение и т.д.

События могут генерироваться как обработчиком, так и агрегировать его до вас. Однако, независимо от того, где они созданы, обработчик команд должен использовать служебную шину для публикации событий. Я предпочитаю генерировать события домена внутри совокупного корня.

С точки зрения DDD, есть только бизнес-концепции и варианты использования. Доменные события, команды, обработчики - это технические детали. Однако все случаи использования домена обычно реализуются как обработчик команд, поэтому обработчики команд должны быть частью домена, а также обработчиками запросов, реализующими запросы, используемые доменом. Запросы, используемые пользовательским интерфейсом, могут быть частью пользовательского интерфейса и т.д.

Точка CQRS должна иметь как минимум 2 модели, а Команда должна быть самой моделью домена. Однако у вас может быть модель Запрос, специализированная для использования в домене, но она все еще является прочитанной (упрощенной) моделью. Рассмотрим модель команды как используемую только для обновлений, модель чтения только для запросов. Но вы можете иметь несколько моделей чтения (для использования определенным уровнем или компонентом) или просто общий (используемый для всего запроса) один.