Ich habe mich in letzter Zeit mit CQRS / MediatR befasst. Aber je mehr ich einen Drilldown durchführe, desto weniger mag ich es. Vielleicht habe ich etwas / alles falsch verstanden.
Es fängt also großartig an, wenn Sie behaupten, Ihren Controller darauf zu reduzieren
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Das passt perfekt zur Richtlinie für dünne Controller. Es werden jedoch einige ziemlich wichtige Details ausgelassen - die Fehlerbehandlung.
Sehen wir uns die Standardaktion Login
aus einem neuen MVC-Projekt an
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);
}
Das Konvertieren stellt uns vor eine Reihe von Problemen in der realen Welt. Denken Sie daran, das Ziel ist es, es zu reduzieren
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Eine mögliche Lösung besteht darin, ein CommandResult<T>
anstelle von a zurückzugeben model
und dann den CommandResult
Filter in einer Nachaktion zu behandeln. Wie hier besprochen .
Eine Implementierung von CommandResult
könnte so sein
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Dies löst jedoch unser Problem in der Login
Aktion nicht wirklich , da es mehrere Fehlerzustände gibt. Wir könnten diese zusätzlichen Fehlerzustände hinzufügen, ICommandResult
aber das ist ein guter Anfang für eine sehr aufgeblähte Klasse / Schnittstelle. Man könnte sagen, es entspricht nicht der Single Responsibility (SRP).
Ein weiteres Problem ist das returnUrl
. Wir haben diesen return RedirectToLocal(returnUrl);
Code. Irgendwie müssen wir bedingte Argumente behandeln, die auf dem Erfolgsstatus des Befehls basieren. Ich glaube zwar, dass dies getan werden könnte (ich bin nicht sicher, ob der ModelBinder FromBody- und FromQuery- returnUrl
Argumente ( is FromQuery) einem einzelnen Modell zuordnen kann ). Man kann sich nur fragen, welche verrückten Szenarien die Straße runterkommen könnten.
Die Modellvalidierung ist zusammen mit der Rückgabe von Fehlermeldungen auch komplexer geworden. Nehmen Sie dies als Beispiel
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Wir fügen dem Modell eine Fehlermeldung bei. Mit einer Exception
Strategie (wie hier vorgeschlagen ) kann so etwas nicht gemacht werden, weil wir das Modell brauchen. Vielleicht können Sie das Modell aus dem Request
herunterladen, aber es wäre ein sehr komplizierter Prozess.
Alles in allem fällt es mir schwer, diese "einfache" Aktion umzusetzen.
Ich suche nach Eingaben. Bin ich hier total im Unrecht?
quelle
Antworten:
Ich denke, Sie erwarten zu viel von dem Muster, das Sie verwenden. CQRS wurde speziell entwickelt, um den Modellunterschied zwischen Abfragen und Befehlen für die Datenbank zu beseitigen. MediatR ist eine reine In-Process-Messaging-Bibliothek. CQRS behauptet nicht, die Notwendigkeit für Geschäftslogik zu beseitigen, wie Sie sie erwarten. CQRS ist ein Muster für den Datenzugriff, aber Ihre Probleme betreffen die Präsentationsebene - Weiterleitungen, Ansichten, Controller.
Möglicherweise wenden Sie das CQRS-Muster falsch auf die Authentifizierung an. Mit login kann es in CQRS nicht als Befehl modelliert werden, weil
Meiner Meinung nach ist die Authentifizierung eine schlechte Domain für CQRS. Bei der Authentifizierung ist ein stark konsistenter, synchroner Anforderungs-Antwort-Fluss erforderlich, damit Sie 1. die Anmeldeinformationen des Benutzers überprüfen 2. eine Sitzung für den Benutzer erstellen 3. alle von Ihnen identifizierten Randfälle behandeln 4. den Benutzer sofort gewähren oder verweigern können In Beantwortung.
CQRS ist ein Muster, das sehr spezifische Verwendungen hat. Es dient dazu, Abfragen und Befehle zu modellieren, anstatt ein Modell für Datensätze zu haben, wie es in CRUD verwendet wird. Wenn Systeme komplexer werden, sind die Anforderungen an Ansichten oft komplexer als das Anzeigen eines einzelnen Datensatzes oder einer Handvoll Datensätze, und eine Abfrage kann die Anforderungen der Anwendung besser modellieren. In ähnlicher Weise können Befehle Änderungen an vielen Datensätzen anstelle von CRUD darstellen, bei denen Sie einzelne Datensätze ändern. Warnt Martin Fowler
Um Ihre Frage zu beantworten, sollte CQRS nicht der erste Ausweg sein, wenn Sie eine Anwendung entwerfen, für die CRUD geeignet ist. Nichts in Ihrer Frage hat mir den Hinweis gegeben, dass Sie einen Grund haben, CQRS zu verwenden.
Was MediatR betrifft, handelt es sich um eine In-Process-Messaging-Bibliothek, mit der Anforderungen von der Anforderungsbearbeitung entkoppelt werden sollen. Sie müssen erneut entscheiden, ob die Verwendung dieser Bibliothek Ihr Design verbessern wird. Ich persönlich bin kein Befürworter von In-Process-Messaging. Loose-Coupling kann auf einfachere Weise als Messaging erreicht werden, und ich würde empfehlen, dass Sie dort beginnen.
quelle
CQRS ist eher eine Datenverwaltungssache als und tendiert nicht dazu, zu stark in eine Anwendungsebene (oder Domain, wenn Sie dies vorziehen, da es in DDD-Systemen am häufigsten verwendet wird) zu bluten. Ihre MVC-Anwendung ist hingegen eine Präsentationsschicht-Anwendung und sollte vom Abfrage- / Persistenzkern des CQRS ziemlich gut getrennt sein.
Eine weitere erwähnenswerte Sache (angesichts Ihres Vergleichs der Standardmethode
Login
und des Wunsches nach Thin Controllern): Ich würde den Standard-ASP.NET-Vorlagen / -Boilerplate-Code nicht genau befolgen, da wir uns um Best Practices kümmern sollten.Ich mag auch dünne Controller, weil sie sehr einfach zu lesen sind. Jeder Controller, den ich normalerweise habe, verfügt über ein "Service" -Objekt, mit dem er gekoppelt ist und das im Wesentlichen die von dem Controller benötigte Logik verarbeitet:
Immer noch dünn genug, aber wir haben nicht wirklich geändert, wie der Code funktioniert. Delegieren Sie einfach die Behandlung an die Dienstmethode, die wirklich keinen anderen Zweck erfüllt, als die Controller-Aktionen einfach zu verdauen.
Bedenken Sie, dass diese Serviceklasse weiterhin dafür verantwortlich ist, die Logik nach Bedarf an das Modell / die Anwendung zu delegieren. Dies ist eigentlich nur eine kleine Erweiterung des Controllers, um den Code übersichtlich zu halten. Die Service-Methoden sind in der Regel auch ziemlich kurz.
Ich bin mir nicht sicher, ob der Mediator konzeptionell etwas anderes tun würde: eine grundlegende Steuerungslogik aus der Steuerung heraus und an einen anderen Ort zu verschieben, um sie zu verarbeiten.
(Ich hatte noch nie von diesem MediatR gehört, und ein kurzer Blick auf die Github-Seite scheint nicht darauf hinzudeuten, dass es bahnbrechend ist - sicherlich nicht so etwas wie CQRS - in der Tat sieht es so aus, als wäre es nur eine weitere Abstraktionsebene kann den Code dadurch komplizieren, dass er einfacher aussieht, aber das ist nur meine erste Einstellung.)
quelle
Ich empfehle Ihnen dringend, Jimmy Bogards NDC-Präsentation zu seinem Ansatz zur Modellierung von http-Anfragen zu lesen: https://www.youtube.com/watch?v=SUiWfhAhgQw
Sie erhalten dann eine klare Vorstellung davon, wofür Mediatr angewendet wird.
Jimmy hält sich nicht blind an Muster und Abstraktionen. Er ist sehr pragmatisch. Mediatr räumt Controller-Aktionen auf. Was die Ausnahmebehandlung betrifft, schiebe ich das in eine übergeordnete Klasse, die so etwas wie Execute heißt. Sie erhalten also eine sehr saubere Controller-Aktion.
Etwas wie:
Die Verwendung sieht ungefähr so aus:
Ich hoffe, das hilft.
quelle
Viele Leute (ich habe es auch getan) verwechseln Muster mit einer Bibliothek. CQRS ist ein Muster, aber MediatR ist eine Bibliothek , mit der Sie dieses Muster implementieren können
Sie können CQRS ohne MediatR oder eine in Bearbeitung befindliche Nachrichtenbibliothek verwenden, und Sie können MediatR ohne CQRS verwenden:
CQS würde so aussehen:
Tatsächlich müssen Sie Ihre Eingabemodelle nicht wie oben beschrieben "Befehle" nennen
CreateProductCommand
. Und Eingabe Ihrer Abfragen "Abfragen". Befehle und Abfragen sind Methoden, keine Modelle.Bei CQRS geht es um die Aufgabentrennung (Lesemethoden müssen an einer von Schreibmethoden getrennten Stelle stehen - isoliert). Es ist eine Erweiterung von CQS, aber der Unterschied besteht darin, dass Sie diese Methoden in eine Klasse einteilen können. (keine Aufteilung der Verantwortung, nur Trennung von Befehlen und Abfragen). Siehe Trennung vs. Trennung
Von https://martinfowler.com/bliki/CQRS.html :
Es gibt Verwirrung in der Aussage, es geht nicht darum, ein separates Modell für Input und Output zu haben, es geht um die Trennung von Verantwortung.
Einschränkung der CQRS- und ID-Generierung
Es gibt eine Einschränkung bei der Verwendung von CQRS oder CQS
Technisch gesehen sollten Befehle in der ursprünglichen Beschreibung keinen Wert (void) zurückgeben, den ich für dumm halte, weil es keine einfache Möglichkeit gibt, eine ID aus einem neu erstellten Objekt zu generieren: /programming/4361889/how-to- get-id-in-create-when-apply-cqrs .
Sie müssen also jedes Mal eine ID generieren, anstatt dies von der Datenbank ausführen zu lassen.
Wenn Sie mehr erfahren möchten: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
quelle