Je me suis penché récemment sur CQRS / MediatR. Mais plus j'explore moins je l'aime. J'ai peut-être mal compris quelque chose / tout.
Cela commence donc génial en prétendant réduire votre contrôleur à ce
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Ce qui correspond parfaitement à la ligne directrice du contrôleur mince. Cependant, il omet certains détails assez importants - la gestion des erreurs.
Regardons l' Login
action par défaut d'un nouveau projet 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);
}
La conversion qui nous présente un tas de problèmes du monde réel. N'oubliez pas que l'objectif est de le réduire à
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Une solution possible à cela consiste à renvoyer un CommandResult<T>
au lieu d'un model
, puis à gérer le CommandResult
filtre dans un post-action. Comme discuté ici .
Une implémentation du CommandResult
pourrait être comme ceci
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Cependant, cela ne résout pas vraiment notre problème dans l' Login
action, car il existe plusieurs états de défaillance. Nous pourrions ajouter ces états d'échec supplémentaires, ICommandResult
mais c'est un bon début pour une classe / interface très gonflée. On pourrait dire qu'il n'est pas conforme à la responsabilité unique (SRP).
Un autre problème est le returnUrl
. Nous avons ce return RedirectToLocal(returnUrl);
morceau de code. D'une manière ou d'une autre, nous devons gérer les arguments conditionnels basés sur l'état de réussite de la commande. Bien que je pense que cela pourrait être fait (je ne sais pas si le ModelBinder peut mapper les arguments FromBody et FromQuery ( returnUrl
est FromQuery) à un seul modèle). On ne peut que se demander quel genre de scénarios fous pourraient se produire sur la route.
La validation des modèles est également devenue plus complexe avec le renvoi de messages d'erreur. Prenez cela comme exemple
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Nous joignons un message d'erreur avec le modèle. Ce genre de chose ne peut pas être fait en utilisant une Exception
stratégie (comme suggéré ici ) parce que nous avons besoin du modèle. Vous pouvez peut-être obtenir le modèle de la Request
mais ce serait un processus très complexe.
Donc, dans l'ensemble, j'ai du mal à convertir cette action "simple".
Je cherche des entrées. Suis-je totalement dans l'erreur ici?
la source
Réponses:
Je pense que vous attendez trop du modèle que vous utilisez. CQRS est spécialement conçu pour traiter la différence de modèle entre les requêtes et les commandes de la base de données , et MediatR est juste une bibliothèque de messagerie en cours. Le CQRS ne prétend pas éliminer le besoin d'une logique métier comme vous vous y attendez. CQRS est un modèle d'accès aux données, mais vos problèmes sont liés à la couche de présentation - redirection, vues, contrôleurs.
Je pense que vous pouvez mal appliquer le modèle CQRS à l'authentification. Avec la connexion, il ne peut pas être modélisé comme une commande dans CQRS car
À mon avis, l'authentification est un domaine médiocre pour CQRS. Avec l'authentification, vous avez besoin d'un flux de demande-réponse synchrone fortement cohérent afin que vous puissiez 1. vérifier les informations d'identification de l'utilisateur 2. créer une session pour l'utilisateur 3. gérer n'importe quelle variété de cas marginaux que vous avez identifiés 4. accorder ou refuser immédiatement l'utilisateur en réponse.
CQRS est un modèle qui a des utilisations très spécifiques. Son but est de modéliser les requêtes et les commandes au lieu d'avoir un modèle pour les enregistrements tel qu'utilisé dans CRUD. À mesure que les systèmes deviennent plus complexes, les demandes de vues sont souvent plus complexes que de simplement montrer un seul enregistrement ou une poignée d'enregistrements, et une requête peut mieux modéliser les besoins de l'application. De même, les commandes peuvent représenter des modifications apportées à de nombreux enregistrements au lieu de CRUD dont vous modifiez des enregistrements uniques. Martin Fowler met en garde
Donc, pour répondre à votre question, le CQRS ne devrait pas être le premier recours lors de la conception d'une application lorsque CRUD convient. Rien dans votre question ne m'a donné d'indication que vous avez une raison d'utiliser le CQRS.
Quant à MediatR, c'est une bibliothèque de messagerie en cours, elle vise à dissocier les requêtes du traitement des requêtes. Vous devez à nouveau décider si cela améliorera votre conception pour utiliser cette bibliothèque. Personnellement, je ne suis pas un partisan de la messagerie en cours. Le couplage lâche peut être réalisé de manière plus simple que la messagerie, et je vous recommande de commencer par là.
la source
Le CQRS est plus une chose de gestion des données plutôt que de ne pas avoir tendance à saigner trop fortement dans une couche d'application (ou domaine si vous préférez, car il a tendance à être le plus souvent utilisé dans les systèmes DDD). Votre application MVC, d'autre part, est une application de couche de présentation et doit être assez bien séparée du noyau de requête / persistance du CQRS.
Une autre chose à noter (compte tenu de votre comparaison de la
Login
méthode par défaut et du désir de contrôleurs légers): je ne suivrais pas exactement les modèles / code passe-partout ASP.NET par défaut comme étant quelque chose dont nous devrions nous soucier pour les meilleures pratiques.J'aime aussi les contrôleurs minces, car ils sont très faciles à lire. Chaque contrôleur que j'ai possède généralement un objet "service" qu'il associe et qui gère essentiellement la logique requise par le contrôleur:
Encore assez mince, mais nous n'avons pas vraiment changé le fonctionnement du code, déléguez simplement la gestion à la méthode de service, qui ne sert vraiment à rien d'autre que de rendre les actions du contrôleur faciles à digérer.
Gardez à l'esprit que cette classe de service est toujours responsable de la délégation de la logique au modèle / à l'application selon les besoins, c'est vraiment juste une légère extension du contrôleur pour garder le code propre. Les méthodes de service sont généralement assez courtes également.
Je ne suis pas sûr que le médiateur ferait quelque chose de conceptuellement différent de cela: déplacer une logique de contrôleur de base hors du contrôleur et vers un autre endroit à traiter.
(Je n'avais jamais entendu parler de ce MediatR auparavant, et un rapide coup d'œil à la page github ne semble pas indiquer que c'est quelque chose de révolutionnaire - certainement pas quelque chose comme CQRS - en fait, il semble être quelque chose comme juste une autre couche d'abstraction que vous peut mettre pour compliquer le code en le rendant plus simple, mais ce n'est que ma première prise)
la source
Je vous recommande fortement de consulter la présentation NDC de Jimmy Bogard sur son approche de la modélisation des requêtes http https://www.youtube.com/watch?v=SUiWfhAhgQw
Vous aurez alors une idée claire de l'utilisation de Mediatr.
Jimmy n'a pas une adhésion aveugle aux motifs et aux abstractions. Il est très pragmatique. Mediatr nettoie les actions du contrôleur. En ce qui concerne la gestion des exceptions, je pousse cela dans une classe parent appelée quelque chose comme Execute. Vous vous retrouvez donc avec une action de contrôleur très propre.
Quelque chose comme:
L'utilisation ressemble un peu à ceci:
J'espère que cela pourra aider.
la source
Beaucoup de gens (je l'ai fait aussi) confondent le motif avec une bibliothèque. CQRS est un modèle, mais MediatR est une bibliothèque que vous pouvez utiliser pour implémenter ce modèle
Vous pouvez utiliser CQRS sans MediatR ou toute bibliothèque de messagerie en cours et vous pouvez utiliser MediatR sans CQRS:
CQS ressemblerait à ceci:
En fait, vous n'avez pas à nommer vos modèles d'entrée "Commandes" comme ci-dessus
CreateProductCommand
. Et saisie de vos requêtes "Requêtes". Les commandes et les requêtes sont des méthodes, pas des modèles.Le CQRS concerne la séparation des responsabilités (les méthodes de lecture doivent être dans un endroit séparé des méthodes d'écriture - isolées). C'est une extension de CQS mais la différence est qu'en CQS vous pouvez mettre ces méthodes dans 1 classe. (pas de séparation des responsabilités, juste une séparation commande-requête). Voir séparation vs ségrégation
Sur https://martinfowler.com/bliki/CQRS.html :
Il y a de la confusion dans ce qu'il dit, il ne s'agit pas d'avoir un modèle distinct pour les entrées et les sorties, il s'agit de la séparation des responsabilités.
CQRS et limitation de génération d'ID
Il y a une limitation à laquelle vous serez confronté lors de l'utilisation de CQRS ou CQS
Techniquement, dans les descriptions originales, les commandes ne devraient renvoyer aucune valeur (void) que je trouve stupide car il n'y a pas de moyen facile d'obtenir l'ID généré à partir d'un objet nouvellement créé: /programming/4361889/how-to- get-id-in-create-when-apply-cqrs .
vous devez donc générer un identifiant à chaque fois au lieu de laisser la base de données le faire.
Si vous voulez en savoir plus: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
la source