Validierung für ein CQS-System, das eine Ausnahme auslöst

8

Ich habe gelesen, dass Ausnahmen nur etwas "Außergewöhnliches" sein sollten und nicht dazu benutzt werden, den Fluss eines Programms zu kontrollieren. Bei einer CQS-Implementierung scheint dies jedoch unmöglich, wenn ich nicht anfange, die Implementierung zu hacken, um damit fertig zu werden. Ich wollte zeigen, wie ich das implementiert habe, um zu sehen, ob das wirklich schlimm ist oder nicht. Ich verwende Decorators, so dass Befehle nichts zurückgeben können (außer Task für Async), so dass ein ValidationResult nicht in Frage kommt. Lass es mich wissen!

In diesem Beispiel wird ASP.NET MVC

verwendet

Controller: (api)

%Vor%

CommandExceptionDecorator ist das erste in der Kette:

%Vor%

Validierung Decorator:

%Vor%

Andere Dekoratoren existieren, sind aber für diese Frage nicht wichtig.

Beispiel für einen Befehl Handler Validator: (Jede Regel wird auf einem eigenen Thread unter den Abdeckungen ausgeführt)

%Vor%

Aktueller Befehlshandler:

%Vor%

Wenn eine Ausnahme für ungültige Validierungsregeln ausgelöst wird, ist der normale Ablauf unterbrochen. Bei jedem Schritt wird davon ausgegangen, dass der vorherige Schritt erfolgreich war. Dies macht den Code sehr sauber, da uns Fehler während der tatsächlichen Implementierung egal sind. Alle Befehle durchlaufen diese Logik, so dass wir sie nur einmal schreiben müssen. An der Spitze von MVC behandle ich die BrokenRuleException wie folgt: (Ich mache AJAX-Aufrufe, nicht ganze Seitenbeiträge)

%Vor%

Die BrokenRule-Klasse enthält die Nachricht und ein Beziehungsfeld. Diese Beziehung ermöglicht es der Benutzeroberfläche, eine Nachricht an etwas auf der Seite zu binden (d. H. An eine Div- oder Formularbeschriftung usw.), um die Nachricht an der richtigen Position anzuzeigen

%Vor%

Wenn ich es nicht so mache, müsste der Controller zuerst eine Validierungsklasse aufrufen, sich die Ergebnisse ansehen und sie dann als 400 mit der richtigen Antwort zurückgeben. Höchstwahrscheinlich müssten Sie eine Hilfsklasse aufrufen, um sie korrekt zu konvertieren. Dann würde der Controller jedoch so oder ähnlich aussehen:

%Vor%

Diese Validierungsprüfung müsste bei jedem einzelnen Befehl wiederholt werden. Wenn es vergessen würde, gäbe es große Konsequenzen. Mit dem Ausnahmestil bleibt der Code kompakt und Entwickler müssen sich nicht jedes Mal darum sorgen, diesen redundanten Code hinzuzufügen.

Ich würde wirklich gerne jedes Feedback bekommen. Danke!

* Bearbeiten * Eine andere mögliche Option wäre, einen anderen "Vermittler" für die Antwort selbst zu haben, der die Validierung zuerst direkt ausführen und dann weitermachen könnte:

%Vor%

In dieser neuen ResultMediator-Klasse würde der CommandValidator nachschlagen, und wenn es Validierungsfehler gäbe, würde BadRequest (new {BrokenRules = brokenRules}) einfach zurückgegeben und als gut bezeichnet. Ist das etwas, das jede Benutzeroberfläche nur erstellen und behandeln muss? Wenn es während dieses Aufrufs eine Ausnahme gibt, müssen wir dies direkt in diesem Vermittler handhaben. Gedanken?

Bearbeiten 2: Vielleicht sollte ich Dekorateure wirklich schnell erklären. Zum Beispiel habe ich diesen CreateCommand (mit einem bestimmten Namespace in diesem Fall). Es gibt einen CommandHandler, der diesen definierten Befehl ICommandHandler behandelt. Diese Schnittstelle hat eine Methode definiert als:

%Vor%

Jeder Dekorator implementiert auch diese selbe Schnittstelle. Mit dem einfachen Injektor können Sie diese neuen Klassen wie CommandHandlerExceptionDecorator und CommandHandlerValidationDecorator über dieselbe Schnittstelle definieren. Wenn der Code an der Spitze den CreateCommandHandler mit diesem CreateCommand aufrufen möchte, ruft SimpleInjector zuerst den letzten definierten Decorator auf (in diesem Fall den ExceptionDecorator). Dieser Decorator behandelt alle Ausnahmen und protokolliert sie für ALLE Befehle, da er generisch definiert ist. Ich muss diesen Code nur einmal schreiben. Es leitet dann den Anruf an den nächsten Dekorator weiter. In diesem Fall könnte es der ValidationDecorator sein. Dies überprüft den CreateCommand, um sicherzustellen, dass er gültig ist. Wenn dies der Fall ist, wird es an den eigentlichen Befehl weitergeleitet, wo die Entität erstellt wird. Wenn nicht, wird eine Ausnahme ausgelöst, da ich nichts zurückgeben kann. CQS gibt an, dass Befehle ungültig sein müssen. Die Aufgabe ist jedoch in Ordnung, da nur der async / await-Stil implementiert werden soll. Es gibt effektiv nichts zurück. Da ich dort keine gebrochenen Regeln zurückgeben kann, schmeiße ich eine Ausnahme. Ich wollte nur wissen, ob dieser Ansatz in Ordnung ist, da er den ganzen Code auf allen für die Aufgabe spezifischen Ebenen (SRP) macht und ich ihn nur einmal für alle Befehle schreiben muss, jetzt und in Zukunft. Jede Benutzerschnittstelle kann einfach jede ausgegebene BrokenRuleException abfangen und weiß, was mit diesen Daten geschehen soll, um sie anzuzeigen. Dies kann generisch geschrieben werden, so dass wir auch Fehler für jeden Befehl anzeigen können (aufgrund der Relation-Eigenschaft der Regel). Auf diese Weise schreiben wir alles einmal und sind fertig. Das Problem ist jedoch, dass ich sehe, dass die Benutzerüberprüfung nicht "außergewöhnlich" ist, also sollten wir keine Ausnahme auslösen.Das Problem dabei ist, dass es meinen Code viel komplexer und weniger wartbar macht, wenn ich diesen Pfad wirklich befolge, da jeder Befehlsaufrufer denselben Code schreiben muss, um das zu tun. Wenn ich nur eine BrokenRuleException für irgendwelche Validierungsfehler werfe, ist das noch in Ordnung?

    
Daniel Lorenz 11.09.2017, 13:41
quelle

3 Antworten

0

Nach Monaten des Hin und Her bin ich zusammengebrochen und habe IResult oder IResult & lt; T & gt; von allen Befehlen / Abfragen. Das IResult sieht so aus:

%Vor%

Es gibt bestimmte Szenarien in meiner Logik, in denen ich einfach keine Ausnahme auslösen kann und die obigen Schichten nur diese booleschen Flags überprüfen lassen, um zu bestimmen, was der Endbenutzer zeigen soll. Wenn eine echte Ausnahme auftritt, kann ich das auf die ErrorMessage-Eigenschaft setzen und von dort abziehen.

Beim Betrachten von CQS wurde mir klar, dass das Zurückgeben eines IResult mit diesem für einen Befehl in Ordnung ist, weil es keine Informationen über den tatsächlichen Prozess zurückgibt. Entweder ist es gelungen (IsSuccessful = true) oder es ist etwas Schlimmes passiert, was bedeutet, dass ich zeigen muss, dass dem Endbenutzer etwas Schlimmes passiert ist und der Befehl nie ausgeführt wurde.

Ich habe einige Hilfsmethoden für das Erstellen von Ergebnissen erstellt, daher ist es den Codern nicht wirklich wichtig. Das einzige, was der Hauptimplementierung hinzugefügt wird, ist:

%Vor%

oder

%Vor%

Auf diese Weise werden die anderen Szenarios von den anderen Dekoratoren behandelt, so dass die Rückgabe eines IResult nicht mühsam wird.

Auf UI-Ebene habe ich stattdessen einen ResponseMediator erstellt, der IActionResult-Elemente zurückgegeben hat. Dies behandelt das IResult und gibt den entsprechenden Daten- / Statuscode zurück. (ICqsMediator ist das, was IMediator war)

%Vor%

Auf diese Weise muss ich keine Ausnahmen behandeln, um den Codefluss zu ändern, außer wenn wirklich etwas Außergewöhnliches passiert. Dies wird so im Exception-Decorator-Handler der obersten Ebene abgefangen:

%Vor%     
Daniel Lorenz 02.01.2018, 19:44
quelle
1

Ich benutze ein sehr ähnliches Muster für meine Befehle, basierend auf MediatR von Jimmy Bogard (mit der Pipeline-Funktion, um mehrere Dekoratoren hinzuzufügen um meine Handler) und Fluent Validation für die Validatoren.

Ich habe einen ähnlichen Gedankenprozess für Sie durchlaufen - meine Validatoren werfen Ausnahmen (die ähnlich wie Ihre an der Spitze von MVC gefangen sind), aber es gibt eine Menge Leute, die Ihnen sagen, dass dies nicht der Fall sein sollte getan werden - nicht zuletzt mein Lieblings-Tech-Orakel Martin Fowler

Ein paar Gedanken:

  • Ich habe einige der "Du sollst nicht ..." - Axiome, denen wir als Entwickler begegnen, ein wenig misstrauisch gemacht und glaube, dass es wichtiger ist, dem Fortschritt in Richtung eines sauberen DRY-Musters für Befehle und Validatoren zu folgen Regel.
  • In ähnlicher Weise könnte "Du sollst nichts von einem Befehl zurückgeben" meiner Meinung nach untergraben werden und scheint Gegenstand einiger debattieren sowieso, so dass Sie das oben verlinkte Benachrichtigungsmuster verwenden können.
  • Und schließlich: Führt Ihre Nutzer-App eine Validierung auf der Client-Seite durch? Vielleicht könnte man argumentieren, dass, wenn die clientseitige Anwendung verhindern sollte, dass der Befehl in einem ungültigen Zustand ist, Ausnahmen auf der Serverseite sowieso wirklich außergewöhnlich wären, und das Problem verschwindet.

Ich hoffe, das hilft auf eine kleine Art und Weise. Ich werde mich für andere Ansichten dazu interessieren.

    
Richard Davis 20.09.2017 10:13
quelle
0

Ich habe die Frage nicht wirklich verstanden, aber ich glaube, diese Ausnahme zu werfen ist in Ordnung. Das Problem ist, dass das Programm in diesem Abschnitt nicht mehr funktioniert und möglicherweise einfriert oder so. Sie sollten eine Popup-Warnmeldung oder etwas haben, das den Benutzer zumindest darüber informiert, was vor sich geht. Geben Sie ihnen eine Zusammenfassung des Fehlers. Sie können dies einfach mit MessageBox.Show In WPF tun.

    
Rohan Shetty 19.09.2017 03:26
quelle