Я в последнее время изучал CQRS / MediatR. Но чем больше я тренируюсь, тем меньше мне это нравится. Возможно, я что-то неправильно понял / все.
Так что все начинается с того, что вы утверждаете, что сводите свой контроллер к этому
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Что идеально сочетается с тонкой направляющей контроллера. Однако это оставляет некоторые довольно важные детали - обработку ошибок.
Давайте посмотрим на Login
действие по умолчанию из нового проекта MVC
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in.");
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning(2, "User account locked out.");
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Преобразование, которое представляет нам кучу реальных проблем. Помните, что цель состоит в том, чтобы уменьшить его до
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Одним из возможных решений этой проблемы является возврат CommandResult<T>
вместо а, model
а затем обработка CommandResult
в фильтре после действия. Как обсуждено здесь .
Одна реализация CommandResult
может быть такой
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Однако это не решает нашу проблему в Login
действии, потому что есть несколько состояний отказа. Мы могли бы добавить эти дополнительные состояния отказа кICommandResult
но это отличное начало для очень раздутого класса / интерфейса. Можно сказать, что это не соответствует Единой Ответственности (SRP).
Другая проблема заключается в returnUrl
. У нас есть этот return RedirectToLocal(returnUrl);
кусок кода. Каким-то образом нам нужно обрабатывать условные аргументы, основанные на состоянии успеха команды. Хотя я думаю, что это можно сделать (я не уверен, что ModelBinder может сопоставить аргументы FromBody и FromQuery ( returnUrl
это FromQuery) одной модели). Можно только задаться вопросом, какие сумасшедшие сценарии могут возникнуть в будущем.
Проверка модели также стала более сложной, наряду с возвратом сообщений об ошибках. Возьми это как пример
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Мы прилагаем сообщение об ошибке вместе с моделью. Такого рода вещи нельзя сделать с помощью Exception
стратегии (как предлагается здесь ), потому что нам нужна модель. Возможно, вы можете получить модель изRequest
но это будет очень сложный процесс.
В общем, мне трудно преобразовать это «простое» действие.
Я ищу входные данные. Я тут совершенно не прав?
источник
Ответы:
Я думаю, что вы ожидаете слишком много от шаблона, который вы используете. CQRS специально разработан для устранения различий в модели между запросом и командами для базы данных , а MediatR - это просто библиотека обмена сообщениями в процессе. CQRS не утверждает, что устраняет необходимость в бизнес-логике, как вы ожидаете. CQRS - это шаблон для доступа к данным, но ваши проблемы связаны с уровнем представления направления, представления, контроллеры.
Я думаю, что вы можете неправильно применять шаблон CQRS для аутентификации. При входе в систему это не может быть смоделировано как команда в CQRS, потому что
На мой взгляд, аутентификация - плохой домен для CQRS. При аутентификации вам нужен строго согласованный, синхронный поток запросов-ответов, чтобы вы могли: 1. проверить учетные данные пользователя 2. создать сеанс для пользователя 3. обработать любое из множества выявленных вами пограничных случаев 4. немедленно предоставить или отклонить пользователя в ответ.
CQRS - это шаблон, который имеет очень специфическое применение. Его цель состоит в том, чтобы моделировать запросы и команды вместо того, чтобы иметь модель для записей, используемых в CRUD. По мере усложнения систем требования к представлениям зачастую становятся более сложными, чем просто показ одной записи или нескольких записей, и запрос может лучше моделировать потребности приложения. Точно так же команды могут представлять изменения во многих записях вместо CRUD, в котором вы изменяете отдельные записи. Мартин Фаулер предупреждает
Поэтому, чтобы ответить на ваш вопрос, CQRS не должен быть первым средством при разработке приложения, когда подходит CRUD. Ничто в вашем вопросе не указывало на то, что у вас есть причина использовать CQRS.
Что касается MediatR, это библиотека обмена сообщениями в процессе, она предназначена для отделения запросов от обработки запросов. Вы должны снова решить, улучшит ли это ваш дизайн, чтобы использовать эту библиотеку. Я лично не сторонник обмена сообщениями в процессе. Слабая связь может быть достигнута более простыми способами, чем обмен сообщениями, и я бы порекомендовал вам начать там.
источник
CQRS скорее относится к управлению данными, чем к частому прикладному уровню (или домену, если вы предпочитаете, так как он чаще всего используется в системах DDD) и не склонен слишком сильно вливаться в него. Приложение MVC, с другой стороны, является приложением уровня представления и должно быть достаточно хорошо отделено от ядра запросов / постоянства CQRS.
Стоит отметить еще одну вещь (учитывая ваше сравнение по умолчанию
Login
и желания использовать тонкие контроллеры): я бы не стал точно следовать шаблонам ASP.NET по умолчанию / шаблонному коду, как о чем-то, о чем мы должны беспокоиться для получения лучших практик.Мне также нравятся тонкие контроллеры, потому что их очень легко читать. У каждого контроллера, который у меня обычно есть, есть «служебный» объект, с которым он соединяется, который по существу обрабатывает логику, требуемую для контроллера:
Все еще достаточно тонкий, но мы на самом деле не изменили, как работает код, просто делегируем обработку методу service, который на самом деле не служит никакой другой цели, кроме упрощения восприятия действий контроллера.
Имейте в виду, что этот класс обслуживания по-прежнему отвечает за делегирование логики модели / приложению по мере необходимости, на самом деле это всего лишь небольшое расширение контроллера для поддержания аккуратного кода. Методы обслуживания, как правило, довольно короткие.
Я не уверен, что посредник будет делать что-то принципиально иное, чем это: переместить некоторую базовую логику контроллера из контроллера в другое место для обработки.
(Я не слышал об этом MediatR раньше, и быстрый взгляд на страницу github, похоже, не указывает на то, что это что-то новаторское - конечно, не что-то вроде CQRS - на самом деле, это похоже на еще один уровень абстракции, который вы можно вставить, чтобы усложнить код, сделав его более простым, но это только мое первоначальное решение)
источник
Я настоятельно рекомендую вам ознакомиться с презентацией Джимми Богарда для NDC о его подходе к моделированию http-запросов. https://www.youtube.com/watch?v=SUiWfhAhgQw
После этого вы получите четкое представление о том, для чего используется Mediatr.
Джимми не имеет слепой приверженности шаблонам и абстракциям. Он очень прагматичный. Медиатр очищает действия контроллера. Что касается обработки исключений, я помещаю это в родительский класс, называемый чем-то вроде Execute. Таким образом, вы получите очень чистое действие контроллера.
Что-то вроде:
Использование выглядит примерно так:
Надеюсь, это поможет.
источник
Многие люди (я тоже так делал) путают шаблон с библиотекой. CQRS - это шаблон, но MediatR - это библиотека, которую вы можете использовать для реализации этого шаблона.
Вы можете использовать CQRS без MediatR или любую библиотеку обмена сообщениями в процессе, и вы можете использовать MediatR без CQRS:
CQS будет выглядеть так:
На самом деле, вам не нужно называть ваши входные модели "Команды", как указано выше
CreateProductCommand
. И ввод ваших запросов "Запросы". Команда и запросы являются методами, а не моделями.CQRS - это разделение ответственности (методы чтения должны быть отделены от методов записи - изолированными). Это расширение CQS, но разница в CQS, вы можете поместить эти методы в 1 класс. (нет разделения ответственности, только разделение команд и запросов). См разделение против сегрегации
С https://martinfowler.com/bliki/CQRS.html :
Существует путаница в том, что в нем говорится, речь идет не о наличии отдельной модели для ввода и вывода, а о разделении ответственности.
CQRS и ограничение генерации идентификатора
Есть одно ограничение, с которым вы столкнетесь при использовании CQRS или CQS
Технически в оригинальном описании команды не должны возвращать никакого значения (void), которое я считаю глупым, потому что нет простого способа получить сгенерированный идентификатор из вновь созданного объекта: /programming/4361889/how-to- get-id-in-create-when-apply-cqrs .
так что вам нужно каждый раз генерировать id, а не позволять базе данных делать это.
Если вы хотите узнать больше: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
источник