Должен ли я поставить командную шину между контроллером и службой домена?

Я работаю над бэкэндом и пытаюсь реализовать шаблоны CQRS. Я довольно ясно разбираюсь в событиях, но иногда борюсь с командами.

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

Я могу внедрить UserService в свой контроллер, который будет обрабатывать логику домена, и именно так работают базовые учебные пособия (я использую Nest.js). Однако я чувствую, что, возможно, именно здесь я должен использовать команду - так что я должен выполнить команду ChangePasswordCommand в своем контроллере, а затем ее обработает модуль домена?

Важно то, что мне нужно возвращаемое значение от команды, что не является проблемой с точки зрения реализации, но это выглядит не очень хорошо с точки зрения CQRS - я должен ДОБАВИТЬ и ПОЛУЧИТЬ одновременно.

Или, может быть, последний вариант — выполнить команду в контроллере, а затем сгенерировать событие (PasswordChangedEvent) в обработчике команд. Затем дождитесь возврата события и верните значение в контроллер.

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

я основываюсь на https://docs.nestjs.com/recipes/cqrs

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
1 419
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

По мере развития вашей архитектуры вы можете обнаружить, что вам требуется командная шина, если вы используете Processes/Sagas для управления рабочими процессами и межагрегатной связью. Если и когда это так, естественно, имеет смысл использовать эту шину для всех команд.

Ниже приведен метод, который я бы предпочел:

execute the command in controller and then emit an event (PasswordChangedEvent) in command handler. Next, wait till event comes back and return the value in controller.

Что касается деталей реализации, то в .NET мы используем службу веб-сокетов SignalR, которая считывает шину событий (где публикуются все события) и пересылает события клиентам, подписавшимся на них.

В этом случае рабочий процесс будет таким:

  1. Пользователь отправляет сообщение контроллеру.
  2. Контроллер добавляет команду в командную шину.
  3. Контроллер возвращает идентификатор, идентифицирующий команду.
  4. Клиент (браузерный клиент) подписывается на события, связанные с этой командой.
  5. Команда принимается службой домена и обрабатывается. Событие отправляется в хранилище событий.
  6. Событие публикуется в шине событий.
  7. Служба подписки прослушивателя событий получает событие, находит подписку и отправляет событие клиенту.
  8. Клиент получает событие и уведомляет пользователя.

Спасибо за ответ. Так что вы в принципе подтвердили мои мысли. Проблема, с которой я столкнулся при таком подходе, заключается в том, что я использую имеют для настройки реализации, а также того, как я взаимодействую между клиентом и сервером, чтобы соответствовать этому шаблону. Я надеялся создать свой бэкэнд/API с использованием CQRS, чтобы он был открыт для быстрых изменений бизнес-логики. Но чтобы добиться этой приятной развязки, теперь мне нужно отказаться от стандартного интерфейса REST и реализовать веб-сокеты — это крутая технология, но для меня это может быть излишним. Я хотел бы каким-то образом инкапсулировать шаблоны команд/событий между запросом и циклом ответа. Есть идеи?

Łukasz Ostrowski 29.06.2019 12:31

@ŁukaszOstrowski Есть много факторов, которые следует учитывать при сдаче CQRS. Я не знаю, является ли быстрое изменение бизнес-логики одним из преимуществ использования этого шаблона. Вы можете применить DDD в стандартной n-уровневой архитектуре и отказаться от накладных расходов CQRS. Что касается REST, вы обычно возвращаете 201 Accepted при отправке команды. Затем ваш клиент дождется завершения команды, прежде чем продолжить. Это могут быть веб-сокеты или опрос на стороне клиента. Или вы могли бы также обработать команду от вашего контроллера и вернуть результат клиенту. Каждый подход имеет свои компромиссы.

CPerson 01.07.2019 14:32

Хорошо, спасибо, я подумаю об этом. Идея в том, что я строю стартап и мне нужно быстро прототипировать, но с другой стороны — все может часто меняться. Внедряя CQRS, я вижу преимущества разделения своих доменов

Łukasz Ostrowski 01.07.2019 14:35

Занимаясь программным бизнесом в течение нескольких лет (используя CQRS+ES) и недавно реализовав свою первую идею «стартапа» (используя стандартный N-уровень и без причудливости), я могу с уверенностью сказать, что вы должны решить, на чем вы сосредоточены. стартап или ориентированы на собственный технологический рост. Любой из них является отличным мотиватором для того, чтобы идти по пути, которым вы идете, но на самом деле это два конкурирующих приоритета. Выбирайте то, что имеет значение, и будьте счастливы. Не выбирайте оба.

CPerson 01.07.2019 15:29

Я согласен. Чего я пытаюсь добиться, так это дополнительных накладных расходов на архитектуру, поэтому я могу отказаться от большего количества реализаций. Архитектура, управляемая событиями, для меня в новинку (в любом случае я обычно не использую бэкенд), но я уже вижу ее преимущества. Никаких причудливых вещей, я не использую источники событий, просто мне нужен более декларативный подход, который я использую во внешних приложениях с Redux Saga. Я далек от инженерии, просто хочу построить стабильный фундамент.

Łukasz Ostrowski 01.07.2019 15:33

Хотя отвечать от @cperson технически верен, я хотел бы добавить к нему несколько нюансов.

Во-первых, то, что может быть неясно из описания ответа, где он советует "генерировать событие (PasswordChangedEvent) в обработчике команд". Это то, что я бы тоже предпочел, но будьте осторожны:

  • Command является частью уровня инфраструктуры, а Event является частью домена.
  • Таким образом, из команды вы должны вызвать код на AggregateRoot, который генерирует событие.
  • Это можно сделать с помощью mergeObjectContext или eventBus.publish (см. NestJS документы).
  • События могут применяться из других объектов предметной области, но генератором обычно является совокупность (при фиксации).

Еще один момент, на который я хотел обратить внимание, заключается в том, что предполагается архитектура, основанная на событиях, то есть применение CQRS/ES. Хотя CQRS часто используется в сочетании с Event Sourcing, ничто не предписывает это делать. Event Sourcing может дать дополнительные преимущества, но также сопряжен со значительной дополнительной сложностью. Вы должны тщательно взвесить все за и против наличия ЭС.

Во многих случаях вам не нужен Event Sourcing. Наличие только CQRS уже дает вам много преимуществ, таких как наличие вашего домена / ограниченных контекстов. Разделение между чтением и записью, команды с одной ответственностью + запросы (в целом более SOLID), более чистая архитектура и т. д. На более высоком уровне легче сместить фокус с «как мне это реализовать (с точки зрения CRUD)?» на 'как эти пользовательские требования вписываются в модель предметной области?'.

Без ES вы можете иметь одну реляционную базу данных и, например. сохраняться с использованием TypeORM. Вы можете сохранять события, но это не нужно. Во многих сценариях вы можете избежать возможной согласованности, когда клиентам необходимо подписываться на события (возможно, вы просто используете их для управления сагами и обновления представлений/проекций на стороне чтения).

Вы всегда можете начать только с CQRS и добавить Event Sourcing позже, когда возникнет необходимость.

Спасибо за ответ. Я полностью увлекаюсь архитектурой, управляемой событиями (разработка приложения начинается с действий, связь через события), и я реализую ее с помощью cqrs, но я не увлекаюсь самой ES. Я согласен, что это отличная концепция (я использую ее на интерфейсе с редуксом), но сложность на сервере слишком высока для моих нужд.

Łukasz Ostrowski 27.06.2020 12:31

Другие вопросы по теме