Используется ли библиотека MediatR в примерах CQRS в Интернете?

#c# #.net #domain-driven-design #cqrs #mediatr

#c# #.net #дизайн, управляемый доменом #cqrs #mediatr

Вопрос:

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

Почти везде я вижу примеры, когда команды и запросы обрабатываются MediatR, но я не вижу от этого никакой пользы, кроме отсутствия необходимости регистрировать каждую команду или запрос в контейнере для внедрения зависимостей. Но затем вам нужно реализовать объекты запроса (наследующие IRequest), обработчики запросов и объекты ответов на запросы, чтобы затем вы могли вызывать метод вашего контроллера API _mediatr.Send(queryObject) .

Почему бы просто не использовать внедрение зависимостей для внедрения объекта запроса в контроллер API, на котором вы можете напрямую вызывать методы «get»? Нравится:

 [HttpGet]
[Route("getall")]
public async Task<IncidentQueryResult> GetAll(int page = 0, int pageSize = 25)
{
    var result = await _incidentQueries.GetIncidents(page, pageSize);
    return resu<
}
 

вместо:

 [HttpGet]
[Route("getall")]
public async Task<IncidentQueryResult> GetAll(int page = 0, int pageSize = 25)
{
    var query = new IncidentQuery(page, pageSize);
    var result = await _mediatr.Send(query);
    return resu<
}
 

Затем внутри GetIncidents метода выполняется прямой sql-вызов базы данных и сопоставление результатов с объектами C #. Простой и понятный.

Для меня идеальным и единственно разумным использованием библиотеки MediatR является обработка событий домена. При реализации DDD я пытаюсь настроить проект так, как показано ниже. Каждый прямоугольник — это отдельный проект в решении. Стрелки представляют ссылки:

DDD со схемой CQRS

Давайте представим сценарий: для создания объекта домена необходимо увеличить счетчик, хранящийся в другом объекте домена (другой агрегат)

  1. К конечной точке API отправляется запрос на добавление какого-либо нового объекта домена в базу данных (уровень 6: презентация)
  2. Метод контроллера использует команду, введенную в его конструктор, для создания объекта домена (уровень 4: команды)
  3. Внутри команды создается новый объект домена вместе с сохраненным в этом объекте событием «создан объект домена», готовым к трансляции непосредственно перед сохранением в базе данных
  4. Затем команда использует репозиторий из уровня инфраструктуры для добавления этого вновь созданного объекта в базу данных.
  5. Затем непосредственно перед сохранением базы данных выполняется: событие «создан объект домена» отправляется через MediatR (уровень 2: инфраструктура)
  6. Затем событие перехватывается на уровне 3: Приложение в одном из обработчиков событий домена.
  7. Обработчик событий домена (уровень 3: приложение) использует репозиторий из уровня инфраструктуры, чтобы получить другой агрегат домена, содержащий счетчик для увеличения, а затем увеличивает счетчик.
  8. Все события домена были обработаны, выполняется сохранение в базу данных.

Итак, MediatR для меня работает только на уровне инфраструктуры и приложений.

Люди просто используют MediatR для команд и запросов только ради его использования? Для меня это выглядит так, как будто добавление обработчиков команд и запросов, типов запросов и запросов и ответов только добавляет больше кода, который не имеет реальной ценности и делает его менее понятным.

Вот несколько ссылок, которые я посетил:

Это также есть на многих сайтах на моем родном языке.

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

Нужно ли вообще использовать MediatR в CQRS?

Ответ №1:

MediatR это всего лишь одна библиотека для решения конкретной задачи. Как указано в репозитории:

Обмен сообщениями в процессе без каких-либо зависимостей.

Разделение ответственности за командный запрос (or CQRS ) — это шаблон проектирования, который может быть реализован многими способами. По сути, пока ваши операции чтения и записи независимы, вы «следуете» этому шаблону.

Так что нет, вы можете создать целое приложение, которое является CQRS «совместимым», без использования MediatR или любой другой библиотеки обмена сообщениями. Тем не менее, вы также можете создать ее как один массивный файл. CQRS это просто один из многих инструментов для управления кодом, который вы можете развернуть в зависимости от ваших потребностей.

Вы также можете использовать MediatR только для обмена сообщениями в процессе и не применять CQRS шаблон.


Тем не менее, распространенная причина, по которой вы, вероятно, видите учебные пособия, блоги и другие CQRS ресурсы MediatR .NET (или аналогичную библиотеку обмена сообщениями), заключается в том, что, как правило, любое приложение, использующее CQRS также, захочет следующее:

  1. Способ проверки на некотором уровне того, что запросы остаются запросами, а команды остаются командами
  2. Большая степень «разделения проблем» применяется ко всему коду в целом.

MediatR как правило, очень хорошо решает обе эти проблемы, отделяя выполнение команды от ее реализации (которая может существовать в отдельном проекте)

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

По сути, в вашей диаграмме MediatR (и / или NServiceBus , Brighter , MassTransit , Rebus … если вам нужно масштабироваться за пределы одного процесса) будет действовать как способ управления потоком данных и отсоединения клиента запроса / команды от его обработчика.


Итак, наконец, чтобы ответить:

Люди просто используют MediatR для команд и запросов только ради его использования?

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


Я рекомендую вам взглянуть на другие его работы Clean Code , чтобы понять, как все эти элементы сочетаются друг с другом, чтобы создать (то, что он называет) «яму успеха» для будущих разработчиков, работающих над проектом. Здесь у него есть репозиторий шаблонов и несколько разговоров об этом в Интернете: https://github.com/jasontaylordev/CleanArchitecture

Ответ №2:

Вы правы в этой MediatR библиотеке и DDD / CQRS по какой-то причине стали синонимами. Но они взаимоисключающие. Вы можете использовать MediatR без доменно-ориентированного проектирования и наоборот. Этот подход стал популярным благодаря своей простоте и доступности множества примеров проектов.

Одна из причин MediatR популярности заключается в том, что она выводит бизнес-логику и логику приложений за пределы Controller , для которых исторически сложно писать тесты.

MediatR шаблон дает вам простой способ разделить эти проблемы. Также доступны другие альтернативы, такие как Service Stack. Та же концепция может быть также использована с архитектурой вертикального среза, которая направлена на разделение кода по функциям.

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

Комментарии:

1. Да, но в 99% контроллеров вы вызываете один метод. Я думаю, что посредник полезен для развязки, но в большинстве случаев достаточно использовать службу за контроллером, и ее проще реализовать и отладить. Я согласен, что шаблон посредника чрезмерно используется в веб-примере.

Ответ №3:

Наибольшее несоответствие, которое я вижу между MediatR и CQRS, заключается в том, что обе команды и запросы проходят через IMediator.Send() метод. Если бы, по крайней мере, существовали отдельные абстракции для команд и запросов, я бы увидел связь с CQRS.

MediatR в лучшем случае нейтрален по отношению к CQRS. Подразумеваемая связь между MediatR и CQRS отсутствует, а статьи, предлагающие ее объяснение, — нет.

Например, один из сообщений связывает MediatR с CQRS таким образом:

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

Подразумевается, что, изучая, как сопоставлять команды и запросы с обработчиками, мы изучаем, как реализовать CQRS. Но CQRS предназначен для разделения запросов и команд. Это не имеет никакого отношения к тому, сопоставляет ли приложение команды обработчикам или нет. Таким образом, статьи, как правило, описывают CQRS, а затем предоставляют детали реализации, которые не имеют ничего общего с CQRS. Популярность таких статей, тем не менее, усиливает представление о том, что MediatR и CQRS неразрывно связаны.

Это не проблема с MediatR. Некоторые статьи не включают MediatR и просто ссылаются на шаблон посредника, по-прежнему не рисуя связь между CQRS и шаблоном посредника. Похоже, это основное недоразумение. Мы можем использовать любое количество шаблонов проектирования в приложении, которое применяет CQRS, но описание как шаблона проектирования, так и CQRS в одной статье не означает, что использование шаблона приводит к CQRS.

В худшем IMediator случае интерфейс препятствует CQRS, потому что он не может различать команды и запросы. Ни MediatR, ни любая другая абстракция не могут помешать обработчику запросов выполнять поведение команды, но если мы разделяем команды и запросы — сердце CQRS — абстракция должна поддерживать это разделение.