it-swarm-eu.dev

Wo sollten wir die Validierung für das Domain-Modell platzieren?

Ich suche immer noch nach bewährten Methoden für die Validierung von Domänenmodellen. Ist das gut, um die Validierung in den Konstruktor des Domänenmodells zu setzen? Beispiel für die Validierung meines Domain-Modells wie folgt:

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 Ihre Vorschläge.

40
adisembiring

Es gibt einen interessanter Artikel von Martin Fowler zu diesem Thema, der einen Aspekt hervorhebt, den die meisten Menschen (einschließlich mir) übersehen:

Aber eine Sache, von der ich denke, dass sie die Leute ständig auslöst, ist, wenn sie glauben, dass die Objektgültigkeit auf eine kontextunabhängige Weise erfolgt, wie es eine isValid-Methode impliziert.

Ich denke, es ist viel nützlicher, sich Validierung als etwas vorzustellen, das an einen Kontext gebunden ist - normalerweise eine Aktion, die Sie ausführen möchten. Ist diese Bestellung gültig, um ausgeführt zu werden, ist dieser Kunde gültig, um im Hotel einzuchecken. Anstatt Methoden wie isValid zu haben, haben Sie Methoden wie isValidForCheckIn.

Daraus folgt, dass der Konstruktor keine Validierung durchführen sollte, außer vielleicht einigen sehr grundlegenden Überprüfungen der geistigen Gesundheit, die von allen Kontexten gemeinsam genutzt werden.

Nochmals aus dem Artikel:

In About Face befürwortete Alan Cooper, dass wir nicht zulassen sollten, dass unsere Vorstellungen von gültigen Zuständen einen Benutzer daran hindern, unvollständige Informationen einzugeben (und zu speichern). Ich wurde vor ein paar Tagen daran erinnert, als ich einen Entwurf eines Buches las, an dem Jimmy Nilsson arbeitet. Er erklärte einen Grundsatz, dass Sie ein Objekt immer speichern können sollten, auch wenn es Fehler enthält. Obwohl ich nicht davon überzeugt bin, dass dies eine absolute Regel sein sollte, denke ich, dass die Leute dazu neigen, das Sparen mehr zu verhindern, als sie sollten. Das Nachdenken über den Kontext für die Validierung kann dies verhindern.

49

Trotz der Tatsache, dass diese Frage etwas abgestanden ist, möchte ich 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 Testhindernisse, 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 ein Objekt in einem Testkabel konstruiert wird, haben Sie Probleme beim Testen Ihrer Klasse.

In Bezug auf die Gültigkeit denke ich gerne an Steuerungssysteme. Steuerungssysteme analysieren ständig den Zustand eines Ausgangs und wenden Korrekturmaßnahmen an, 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, weshalb alle realen Regelungen normalerweise Regelungen verwenden.

Ich denke, dass die Verwendung einer kontextabhängigen Validierung und einfach zu erstellender Objekte die Arbeit mit Ihrem System später erleichtern wird.

6
Paul

Wie Sie sicher schon wissen ...

Bei der objektorientierten Programmierung ist ein Konstruktor (manchmal abgekürzt als ctor) in einer Klasse eine spezielle Art von Unterroutine, die beim Erstellen eines Objekts aufgerufen wird. Es bereitet das neue Objekt für die Verwendung vor und akzeptiert häufig Parameter, mit denen der Konstruktor alle beim ersten Erstellen des Objekts erforderlichen Elementvariablen festlegt. Es wird als Konstruktor bezeichnet, da es die Werte von Datenelementen der Klasse erstellt.

Die Überprüfung der Gültigkeit der als c'tor-Parameter übergebenen Daten ist definitiv gültig im Konstruktor - andernfalls erlauben Sie möglicherweise die Erstellung eines ungültigen Objekts.

Allerdings (und dies ist nur meine Meinung, kann derzeit keine guten Dokumente finden) - wenn die Datenüberprüfung komplexe Vorgänge erfordert (z. B. asynchrone Vorgänge - möglicherweise serverbasierte Validierung bei der Entwicklung einer Desktop-App), ist dies besser Geben Sie eine Initialisierungs- oder explizite Validierungsfunktion ein, und setzen Sie die Elemente im c'tor auf Standardwerte (z. B. null).


Ebenso wie eine Randnotiz, wie Sie sie in Ihr Codebeispiel aufgenommen haben ...

Wenn Sie in AddOrderLine keine weitere Validierung (oder andere Funktionalität) durchführen, würde ich höchstwahrscheinlich das List<LineItem> als Eigenschaft, anstatt dass Order als Fassade fungiert.

4
Demian Brecht

Die Validierung sollte so bald wie möglich durchgeführt werden.

Die Validierung in jedem Kontext, weder im Domain-Modell noch in einer anderen Art, Software zu schreiben, sollte dem Zweck dienen, WAS Sie validieren möchten und auf welcher Ebene Sie sich gerade befinden.

Basierend auf Ihrer Frage würde die Antwort wohl darin bestehen, die Validierung aufzuteilen.

  1. Die Eigenschaftsüberprüfung prüft, ob der Wert für diese Eigenschaft korrekt ist, z. wenn ein Bereich zwischen 1-10 erwartet wird.

  2. Die Objektvalidierung garantiert, dass alle Eigenschaften des Objekts in Verbindung miteinander gültig sind. z.B. BeginDate steht 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 gilt. Diese Regel sollte überprüft werden, nachdem alle Eigenschaften festgelegt wurden. Dies kann auf der aggregierten Stammebene aufgerufen werden

  3. 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 die OrderLines. Aber dann besagt eine Geschäftsregel, dass keine Bestellung über 1.000 US-Dollar liegen darf. Wie würden Sie diese Regel in einigen Fällen durchsetzen? IS erlaubt. 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 Bitte" aus dem Weg zu räumen).

  4. als nächstes erfolgt die Validierung auf der Präsentationsebene. Werden Sie das Objekt wirklich über das Netzwerk senden, da Sie wissen, dass es fehlschlagen wird? Oder ersparen Sie dem Benutzer diese Bürde und informieren ihn, sobald er einen ungültigen Wert eingibt. z.B. In den meisten Fällen ist Ihre DEV-Umgebung langsamer als die Produktion. Möchten Sie 30 Sekunden warten, bevor Sie darüber informiert werden, dass Sie dieses Feld während eines weiteren Testlaufs WIEDER vergessen haben, insbesondere wenn ein Produktionsfehler behoben werden muss, bei dem Ihr Chef Ihnen den Hals runter atmet?

  5. Die Validierung auf Persistenzstufe soll der Validierung des Eigenschaftswerts so nahe wie möglich kommen. Dies hilft, Ausnahmen beim Lesen von "null" - oder "ungültigen 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 WIEDER ausgeführt wird. Und gespeicherte Prozeduren sind die DB-Verwaltungsdomäne. Versuchen Sie also nicht, auch SEINE Arbeit zu erledigen (oder stören Sie ihn schlimmer noch mit dieser "kleinen Auswahl, für die er nicht bezahlt wird".

um es mit einigen berühmten Worten zu sagen "es kommt darauf an", aber zumindest wissen Sie jetzt, WARUM es darauf ankommt.

Ich wünschte, ich könnte das alles an einem einzigen Ort platzieren, aber das geht leider nicht. Dies würde eine Abhängigkeit von einem "Gott-Objekt" darstellen, das ALLE Validierung für ALLE Ebenen enthält. Du willst diesen dunklen Weg nicht gehen.

Aus diesem Grund werfe ich nur Validierungsausnahmen auf Eigenschaftsebene. Alle anderen Ebenen Ich verwende ValidationResult mit einer IsValid-Methode, um alle "fehlerhaften Regeln" zu sammeln und sie in einer einzigen AggregateException an den Benutzer zu übergeben.

Wenn ich den Aufrufstapel weitergebe, sammle ich diese erneut in AggregateExceptions, bis ich die Präsentationsschicht erreiche. Die Service-Schicht kann diese Ausnahme im Fall von WCF als FaultException direkt an den Client senden.

Auf diese Weise kann ich die Ausnahme nehmen und sie entweder aufteilen, um einzelne Fehler bei jedem Eingabesteuerelement anzuzeigen, oder sie reduzieren und in einer einzigen 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 Aggregationsebene (oder auf Serviceebene, wenn Sie möchten) habe, liegt es 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. Sie verwalten Anwendung A, aber Anwendung B speist einige Daten über Ihren Dienst ein. Ratet mal, wen sie zuerst fragen, wenn es einen Fehler gibt? Der Administrator von Anwendung B informiert den Benutzer gerne darüber, dass an meinem Ende kein Fehler vorliegt. Ich gebe nur die Daten ein.

4
Wesley Kenis