He estado buscando en CQRS / MediatR últimamente. Pero cuanto más profundizo, menos me gusta. Quizás he entendido mal algo / todo.
Por lo tanto, comienza increíble al afirmar que reduce su controlador a esto
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Que encaja perfectamente con la guía del controlador delgado. Sin embargo, deja de lado algunos detalles bastante importantes: manejo de errores.
Veamos la Login
acción predeterminada de un nuevo proyecto 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);
}
Conversión que nos presenta un montón de problemas del mundo real. Recuerde que el objetivo es reducirlo a
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Una posible solución a esto es devolver un en CommandResult<T>
lugar de a model
y luego manejarlo CommandResult
en un filtro posterior a la acción. Como se discutió aquí .
Una implementación de la CommandResult
podría ser así
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Sin embargo, eso realmente no resuelve nuestro problema en la Login
acción, porque hay múltiples estados de falla. Podríamos agregar estos estados de falla adicionales, ICommandResult
pero ese es un gran comienzo para una clase / interfaz muy hinchada. Se podría decir que no cumple con la responsabilidad única (SRP).
Otro problema es el returnUrl
. Tenemos este return RedirectToLocal(returnUrl);
pedazo de código. De alguna manera, necesitamos manejar argumentos condicionales basados en el estado de éxito del comando. Si bien creo que podría hacerse (no estoy seguro de si ModelBinder puede asignar argumentos FromBody y FromQuery ( returnUrl
es FromQuery) a un solo modelo). Uno solo puede preguntarse qué tipo de escenarios locos podrían venir en el futuro.
La validación del modelo también se ha vuelto más compleja junto con la devolución de mensajes de error. Toma esto como un ejemplo
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Adjuntamos un mensaje de error junto con el modelo. Este tipo de cosas no se pueden hacer usando una Exception
estrategia (como se sugiere aquí ) porque necesitamos el modelo. Quizás pueda obtener el modelo del, Request
pero sería un proceso muy complicado.
Así que, en general, me está costando convertir esta acción "simple".
Estoy buscando entradas. ¿Estoy totalmente equivocado aquí?
fuente
Respuestas:
Creo que esperas demasiado del patrón que estás usando. CQRS está diseñado específicamente para abordar la diferencia de modelo entre la consulta y los comandos de la base de datos , y MediatR es solo una biblioteca de mensajería en proceso. CQRS no pretende eliminar la necesidad de una lógica empresarial como espera que lo hagan. CQRS es un patrón para el acceso a datos, pero sus problemas son con la capa de presentación: redireccionamientos, vistas, controladores.
Creo que puede estar aplicando mal el patrón CQRS a la autenticación. Con el inicio de sesión, no se puede modelar como un comando en CQRS porque
En mi opinión, la autenticación es un dominio deficiente para CQRS. Con la autenticación, necesita un flujo de solicitud-respuesta síncrono muy consistente para que pueda 1. verificar las credenciales del usuario 2. crear una sesión para el usuario 3. manejar cualquiera de la variedad de casos extremos que ha identificado 4. otorgar o denegar inmediatamente al usuario en respuesta.
CQRS es un patrón que tiene usos muy específicos. Su propósito es modelar consultas y comandos en lugar de tener un modelo para registros como se usa en CRUD. A medida que los sistemas se vuelven más complejos, las demandas de vistas a menudo son más complejas que solo mostrar un solo registro o un puñado de registros, y una consulta puede modelar mejor las necesidades de la aplicación. Del mismo modo, los comandos pueden representar cambios en muchos registros en lugar de CRUD, que cambia los registros individuales. Martin Fowler advierte
Entonces, para responder a su pregunta, CQRS no debería ser el primer recurso al diseñar una aplicación cuando CRUD es adecuado. Nada en su pregunta me dio la indicación de que tiene una razón para usar CQRS.
En cuanto a MediatR, es una biblioteca de mensajería en proceso, tiene como objetivo desacoplar las solicitudes del manejo de solicitudes. Debe decidir nuevamente si mejorará su diseño para usar esta biblioteca. Personalmente no soy un defensor de la mensajería en proceso. El acoplamiento suelto se puede lograr de formas más simples que la mensajería, y le recomendaría que comience allí.
fuente
CQRS es más una cuestión de gestión de datos y no tiende a sangrar demasiado en una capa de aplicación (o Dominio si lo prefiere, ya que tiende a usarse con mayor frecuencia en los sistemas DDD). Su aplicación MVC, por otro lado, es una aplicación de capa de presentación y debe estar bastante bien separada del núcleo de consulta / persistencia del CQRS.
Otra cosa que vale la pena señalar (dada su comparación del
Login
método predeterminado y el deseo de controladores delgados): no seguiría exactamente las plantillas / código estándar repetitivo de ASP.NET como algo por lo que deberíamos preocuparnos por las mejores prácticas.También me gustan los controladores delgados, porque son muy fáciles de leer. Cada controlador que tengo generalmente tiene un objeto de "servicio" con el que se empareja y que esencialmente maneja la lógica requerida por el controlador:
Todavía lo suficientemente delgado, pero no hemos cambiado realmente cómo funciona el código, solo delegue el manejo al método de servicio, que realmente no tiene otro propósito que hacer que las acciones del controlador sean fáciles de digerir.
Tenga en cuenta que esta clase de servicio sigue siendo responsable de delegar la lógica al modelo / aplicación según sea necesario, en realidad es solo una ligera extensión del controlador para mantener el código ordenado. Los métodos de servicio son generalmente bastante cortos también.
No estoy seguro de que el mediador esté haciendo algo conceptualmente diferente a eso: mover alguna lógica básica del controlador fuera del controlador y en otro lugar para ser procesado.
(No había oído hablar de este MediatR antes, y un vistazo rápido a la página de github no parece indicar que sea algo innovador, ciertamente no algo como CQRS, de hecho, parece ser algo más que otra capa de abstracción que usted puede complicar el código haciendo que parezca más simple, pero esa es solo mi primera toma)
fuente
Le recomiendo que vea la presentación NDC de Jimmy Bogard sobre su enfoque para modelar solicitudes http https://www.youtube.com/watch?v=SUiWfhAhgQw
Entonces obtendrá una idea clara de para qué se utiliza Mediatr.
Jimmy no tiene una adhesión ciega a los patrones y abstracciones. El es muy pragmático. Mediatr limpia las acciones del controlador. En cuanto al manejo de excepciones, inserto eso en una clase principal llamada algo como Ejecutar. Entonces terminas con una acción de controlador muy limpia.
Algo como:
El uso se parece un poco a esto:
Espero que ayude.
fuente
Mucha gente (yo también lo hice) confunde el patrón con una biblioteca. CQRS es un patrón, pero MediatR es una biblioteca que puede usar para implementar ese patrón
Puede usar CQRS sin MediatR o cualquier biblioteca de mensajería en proceso y puede usar MediatR sin CQRS:
CQS se vería así:
De hecho, no tiene que nombrar a sus modelos de entrada "Comandos" como se indica arriba
CreateProductCommand
. Y entrada de sus consultas "Consultas". Los comandos y las consultas son métodos, no modelos.CQRS trata sobre la segregación de responsabilidad (los métodos de lectura deben estar en un lugar separado de los métodos de escritura, aislados). Es una extensión de CQS, pero la diferencia está en CQS, puede colocar estos métodos en 1 clase. (sin segregación de responsabilidad, solo separación de comando-consulta). Ver separación vs segregación
Desde https://martinfowler.com/bliki/CQRS.html :
Hay confusión en lo que dice, no se trata de tener un modelo separado para entrada y salida, se trata de la separación de responsabilidades.
CQRS y limitación de generación de identificación
Hay una limitación que enfrentará al usar CQRS o CQS
Técnicamente, en la descripción original, los comandos no deberían devolver ningún valor (nulo) que me parece estúpido porque no hay una manera fácil de obtener la identificación generada de un objeto recién creado: /programming/4361889/how-to- get-id-in-create-when-apply-cqrs .
por lo que debe generar una identificación cada vez, en lugar de dejar que la base de datos lo haga.
Si desea obtener más información: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
fuente