Ich suche immer noch nach bewährten Methoden für die Validierung des Domain-Modells. Ist das gut, um die Validierung im Konstruktor des Domain-Modells zu platzieren? Beispiel für die Validierung meines Domain-Modells:
public class Order
{
private readonly List<OrderLine> _lineItems;
public virtual Customer Customer { get; private set; }
public virtual DateTime OrderDate { get; private set; }
public virtual decimal OrderTotal { get; private set; }
public Order (Customer customer)
{
if (customer == null)
throw new ArgumentException("Customer name must be defined");
Customer = customer;
OrderDate = DateTime.Now;
_lineItems = new List<LineItem>();
}
public void AddOderLine //....
public IEnumerable<OrderLine> AddOderLine { get {return _lineItems;} }
}
public class OrderLine
{
public virtual Order Order { get; set; }
public virtual Product Product { get; set; }
public virtual int Quantity { get; set; }
public virtual decimal UnitPrice { get; set; }
public OrderLine(Order order, int quantity, Product product)
{
if (order == null)
throw new ArgumentException("Order name must be defined");
if (quantity <= 0)
throw new ArgumentException("Quantity must be greater than zero");
if (product == null)
throw new ArgumentException("Product name must be defined");
Order = order;
Quantity = quantity;
Product = product;
}
}
Vielen Dank für all Ihren Vorschlag.
quelle
Trotz der Tatsache, dass diese Frage etwas abgestanden ist, möchte ich noch etwas hinzufügen, das sich lohnt:
Ich möchte @MichaelBorgwardt zustimmen und die Testbarkeit erweitern. In "Effektiv mit Legacy-Code arbeiten" spricht Michael Feathers viel über Hindernisse beim Testen, und eines dieser Hindernisse ist "schwer zu konstruierende" Objekte. Das Konstruieren eines ungültigen Objekts sollte möglich sein, und wie Fowler vorschlägt, sollten kontextabhängige Gültigkeitsprüfungen in der Lage sein, diese Bedingungen zu identifizieren. Wenn Sie nicht herausfinden können, wie Sie ein Objekt in einem Testgeschirr konstruieren, können Sie Ihre Klasse nur schwer testen.
Bezüglich der Gültigkeit denke ich gerne an Steuerungssysteme. Steuerungssysteme analysieren ständig den Zustand eines Ausgangs und ergreifen Korrekturmaßnahmen, wenn der Ausgang vom Sollwert abweicht. Dies wird als Regelung bezeichnet. Die Regelung erwartet von sich aus Abweichungen und korrigiert diese. So funktioniert die reale Welt. Deshalb verwenden alle realen Steuerungssysteme in der Regel Regler.
Ich denke, dass die Verwendung von kontextabhängiger Validierung und einfach zu konstruierenden Objekten die Arbeit mit Ihrem System später erleichtert.
quelle
Wie Sie sicher schon wissen ...
Die Überprüfung der Gültigkeit der als c'tor-Parameter übergebenen Daten ist im Konstruktor definitiv gültig. Andernfalls können Sie möglicherweise die Erstellung eines ungültigen Objekts zulassen .
Allerdings (und dies ist nur meine Meinung, ich kann derzeit noch keine guten Dokumente finden) - wenn die Datenüberprüfung komplexe Vorgänge erfordert (z. B. asynchrone Vorgänge - möglicherweise serverbasierte Überprüfung bei der Entwicklung einer Desktop-App), ist es besser
null
Geben Sie eine Art Initialisierungs- oder explizite Validierungsfunktion ein, und setzen Sie die Elemente in c'tor auf Standardwerte (z. B. ).Auch nur als Randnotiz, wie Sie es in Ihrem Codebeispiel aufgenommen haben ...
Sofern Sie keine weitere Validierung (oder andere Funktionen) in durchführen
AddOrderLine
, würde ich die höchstwahrscheinlichList<LineItem>
als eine Eigenschaft aussetzen, anstatt als Fassade zuOrder
fungieren .quelle
AddLineItem
Methode zu haben . Tatsächlich ist dies für DDD bevorzugt. WennList<LineItem>
in ein benutzerdefiniertes Auflistungsobjekt geändert wird, können sich die offen gelegte Eigenschaft und alle von einerList<LineItem>
Eigenschaft abhängigen Änderungen, Fehler und Ausnahmen ergeben.Die Validierung sollte so bald wie möglich durchgeführt werden.
Die Validierung in jedem Kontext, unabhängig vom Domain-Modell oder einer anderen Art, Software zu schreiben, sollte dem Zweck dienen, WAS Sie validieren möchten und auf welcher Ebene Sie sich gerade befinden.
Aufgrund Ihrer Frage würde die Antwort wohl darin bestehen, die Validierung aufzuteilen.
Die Eigenschaftsüberprüfung prüft, ob der Wert für diese Eigenschaft korrekt ist, z. B. wenn ein Bereich zwischen 1 und 10 erwartet wird.
Die Objektvalidierung garantiert, dass alle Eigenschaften des Objekts in Verbindung miteinander gültig sind. zB BeginDate liegt vor EndDate. Angenommen, Sie lesen einen Wert aus dem Datenspeicher und sowohl BeginDate als auch EndDate werden standardmäßig mit DateTime.Min initialisiert. Beim Festlegen des BeginDate gibt es keinen Grund, die Regel "muss vor dem EndDate sein" durchzusetzen, da dies NOCH nicht zutrifft. Diese Regel sollte überprüft werden, nachdem alle Eigenschaften festgelegt wurden. Dies kann auf der aggregierten Stammebene aufgerufen werden
Die Validierung sollte auch für die aggregierte (oder aggregierte Stamm-) Entität durchgeführt werden. Ein Order-Objekt kann gültige Daten enthalten, ebenso wie seine OrderLines. In einer Geschäftsregel heißt es jedoch, dass kein Auftrag mehr als 1.000 US-Dollar betragen darf. Wie würden Sie diese Regel in einigen Fällen durchsetzen, ist dies zulässig? Sie können nicht einfach eine Eigenschaft "Betrag nicht validieren" hinzufügen, da dies zu Missbrauch führen würde (früher oder später, vielleicht sogar Sie, nur um diese "böse Anfrage" aus dem Weg zu räumen).
Als nächstes erfolgt die Validierung auf der Präsentationsebene. Wollen Sie das Objekt wirklich über das Netzwerk senden, wenn Sie wissen, dass es fehlschlagen wird? Oder ersparen Sie dem Benutzer diesen Aufwand und informieren ihn, sobald er einen ungültigen Wert eingibt. ZB ist Ihre DEV-Umgebung in den meisten Fällen langsamer als die Produktion. Möchten Sie 30 Sekunden warten, bevor Sie über "Sie haben dieses Feld WIEDER während eines weiteren Testlaufs vergessen" informiert werden, insbesondere wenn ein Produktionsfehler behoben werden muss, bei dem Ihr Chef Ihnen den Hals runter atmet?
Die Validierung auf der Persistenzebene sollte der Validierung des Eigenschaftswerts so nahe wie möglich kommen. Dies hilft dabei, Ausnahmen beim Lesen von "Null" - oder "Ungültiger Wert" -Fehlern zu vermeiden, wenn Mapper jeglicher Art oder einfache alte Datenleser verwendet werden. Die Verwendung gespeicherter Prozeduren löst dieses Problem, erfordert jedoch, dass dieselbe Bewertungslogik WIEDER geschrieben und erneut ausgeführt wird. Gespeicherte Prozeduren sind die Domäne des DB-Administrators. Versuchen Sie also nicht, auch SEINE Arbeit zu erledigen (oder stören Sie ihn noch schlimmer mit dieser "netten Auswahl, für die er nicht bezahlt wird").
Um es mit einigen berühmten Worten zu sagen: "es kommt darauf an", aber spätestens jetzt wissen Sie, WARUM es darauf ankommt.
Ich wünschte, ich könnte das alles an einem einzigen Ort unterbringen, aber das ist leider nicht möglich. Dies würde eine Abhängigkeit von einem "Gott-Objekt" erzeugen, das die ALL-Validierung für ALLE Ebenen enthält. Du willst diesen dunklen Weg nicht gehen.
Aus diesem Grund werfe ich nur Validierungsausnahmen einer Eigenschaftsstufe. Alle anderen Ebenen Ich verwende ValidationResult mit einer IsValid-Methode, um alle "defekten Regeln" zu sammeln und sie in einer einzigen AggregateException an den Benutzer zu übergeben.
Bei der Weitergabe des Aufrufstapels sammle ich diese dann erneut in AggregateExceptions, bis ich die Präsentationsebene erreiche. Die Serviceebene kann diese Ausnahme im Fall von WCF als FaultException direkt an den Client senden.
Auf diese Weise kann ich die Ausnahme annehmen und sie entweder aufteilen, um einzelne Fehler bei jedem Eingabesteuerelement anzuzeigen, oder sie reduzieren und in einer einzelnen Liste anzeigen. Es ist deine Entscheidung.
Aus diesem Grund habe ich auch die Validierung der Präsentation erwähnt, um diese so kurz wie möglich zu halten.
Falls Sie sich fragen, warum ich die Validierung auch auf der Aggregationsebene (oder auf der Serviceebene, wenn Sie möchten) habe, liegt dies daran, dass ich keine Kristallkugel habe, die mir sagt, wer meine Services in Zukunft nutzen wird. Sie werden genug Probleme haben, Ihre eigenen Fehler zu finden, um zu verhindern, dass andere Ihre Fehler machen :), indem Sie ungültige Daten eingeben. Beispielsweise verwalten Sie Anwendung A, aber Anwendung B füttert einige Daten mit Ihrem Dienst. Ratet mal, wen sie zuerst fragen, wenn es einen Bug gibt? Der Administrator von Anwendung B teilt dem Benutzer mit, dass kein Fehler aufgetreten ist. Ich gebe nur die Daten ein.
quelle