Freigeben über


Designvalidierungen in der Domänenmodellschicht

Tipp

Dieser Inhalt ist ein Auszug aus dem eBook .NET Microservices Architecture for Containerized .NET Applications, verfügbar auf .NET Docs oder als kostenlose herunterladbare PDF, die offline gelesen werden kann.

.NET-Mikroservices-Architektur für containerisierte .NET-Anwendungen eBook-Cover-Miniaturansicht.

In DDD können Gültigkeitsprüfungsregeln als Invarianten betrachtet werden. Die Hauptverantwortung eines Aggregats besteht darin, Invarianten bei Zustandsänderungen für alle Entitäten innerhalb dieses Aggregats sicherzustellen.

Domänenentitäten sollten immer gültige Entitäten sein. Es gibt eine bestimmte Anzahl von Invarianten für ein Objekt, das immer wahr sein sollte. Beispielsweise muss ein Auftragselementobjekt immer eine Menge aufweisen, die eine positive ganze Zahl sein muss, sowie einen Artikelnamen und -preis. Daher liegt die Invariantenerzwingung in der Verantwortung der Domänenentitäten (insbesondere des Aggregatstamms), und ein Entitätsobjekt sollte nicht vorhanden sein, ohne gültig zu sein. Invariante Regeln werden einfach als Verträge ausgedrückt, und Ausnahmen oder Benachrichtigungen werden ausgelöst, wenn sie verletzt werden.

Der Grund dafür ist, dass viele Fehler auftreten, weil sich Objekte in einem Zustand befinden, in dem sie nie sein dürften.

Wir schlagen nun vor, dass wir jetzt über einen SendUserCreationEmailService verfügen, der ein UserProfile akzeptiert ... wie können wir in diesem Dienst rationalisieren, dass der Name nicht null ist? Überprüfen wir es erneut? Oder wahrscheinlicher ... Sie machen sich nicht die Mühe zu prüfen und "hoffen auf das Beste" – Sie hoffen, dass jemand es überprüft hat, bevor es an Sie gesendet wird. Natürlich sollten wir mit TDD eines der ersten Tests schreiben, dass, wenn ich einen Kunden mit einem Nullnamen sende, dass er einen Fehler auslösen sollte. Aber sobald wir beginnen, diese Art von Tests immer wieder zu schreiben, erkennen wir ... "Was ist, wenn wir nie zulassen, dass der Name null wird? wir hätten nicht alle diese Tests!".

Implementieren von Validierungen in der Domänenmodellschicht

Validierungen werden in der Regel in Domänenentitätskonstruktoren oder in Methoden implementiert, die die Entität aktualisieren können. Es gibt mehrere Möglichkeiten zum Implementieren von Validierungen, z. B. Überprüfen von Daten und Auslösen von Ausnahmen, wenn die Überprüfung fehlschlägt. Es gibt auch komplexere Muster, z. B. die Verwendung des Spezifikationsmusters für Überprüfungen, und das Benachrichtigungsmuster, um eine Auflistung von Fehlern zurückzugeben, anstatt eine Ausnahme für jede Überprüfung zurückzugeben, während sie auftritt.

Überprüfen von Bedingungen und Auslösen von Ausnahmen

Das folgende Codebeispiel zeigt den einfachsten Ansatz zur Überprüfung in einer Domänenentität, indem eine Ausnahme ausgelöst wird. In der Referenztabelle am Ende dieses Abschnitts finden Sie Links zu erweiterten Implementierungen basierend auf den zuvor behandelten Mustern.

public void SetAddress(Address address)
{
    _shippingAddress = address?? throw new ArgumentNullException(nameof(address));
}

Ein besseres Beispiel zeigt, dass sichergestellt werden muss, dass sich der interne Zustand nicht geändert hat oder dass alle Mutationen für eine Methode aufgetreten sind. Die folgende Implementierung würde z. B. das Objekt in einem ungültigen Zustand belassen:

public void SetAddress(string line1, string line2,
    string city, string state, int zip)
{
    _shippingAddress.line1 = line1 ?? throw new ...
    _shippingAddress.line2 = line2;
    _shippingAddress.city = city ?? throw new ...
    _shippingAddress.state = (IsValid(state) ? state : throw new …);
}

Wenn der Wert des Bundeslandes ungültig ist, wurden die erste Adresszeile und die Stadt bereits geändert. Dies kann dazu führen, dass die Adresse ungültig ist.

Ein ähnlicher Ansatz kann im Konstruktor der Entität verwendet werden, wodurch eine Ausnahme ausgelöst wird, um sicherzustellen, dass die Entität nach der Erstellung gültig ist.

Verwenden von Überprüfungsattributen im Modell basierend auf Datenanmerkungen

Datenanmerkungen wie die Attribute "Erforderlich" oder "MaxLength" können verwendet werden, um EF Core-Datenbankfeldeigenschaften zu konfigurieren, wie im Abschnitt "Tabellenzuordnung " ausführlich erläutert, aber sie funktionieren nicht mehr für die Entitätsüberprüfung in EF Core (auch nicht die IValidatableObject.Validate Methode), wie sie seit EF 4.x in .NET Framework getan haben.

Datenanmerkungen und die IValidatableObject Schnittstelle können während der Modellbindung weiterhin für die Modellvalidierung verwendet werden, bevor die Aktionen des Controllers wie gewohnt aufgerufen werden. Dieses Modell soll jedoch ein ViewModel oder DTO sein und ist eine Angelegenheit von MVC oder API und betrifft nicht das Domänenmodell.

Nachdem Sie den konzeptionellen Unterschied deutlich gemacht haben, können Sie weiterhin Datenanmerkungen und IValidatableObject in der Entitätsklasse zur Überprüfung verwenden, wenn Ihre Aktionen einen Entitätsklassenobjektparameter erhalten, der nicht empfohlen wird. In diesem Fall erfolgt die Überprüfung bei der Modellbindung, direkt vor dem Aufrufen der Aktion, und Sie können die ModelState.IsValid-Eigenschaft des Controllers überprüfen, um das Ergebnis zu überprüfen, aber dann wieder geschieht es im Controller, nicht bevor das Entitätsobjekt im DbContext beibehalten wird, wie es seit EF 4.x getan wurde.

Sie können weiterhin eine benutzerdefinierte Überprüfung in der Entitätsklasse mithilfe von Datenanmerkungen und der IValidatableObject.Validate Methode implementieren, indem Sie die SaveChanges-Methode von DbContext überschreiben.

Sie können eine Beispielimplementierung für die Überprüfung von IValidatableObject Entitäten in diesem Kommentar auf GitHub sehen. In diesem Beispiel werden keine attributbasierten Überprüfungen durchgeführt, aber sie sollten leicht mithilfe der Reflexion in derselben Überschreibung implementiert werden können.

Aus DDD-Sicht ist das Domänenmodell jedoch am besten schlanker mit der Verwendung von Ausnahmen in den Verhaltensmethoden Ihrer Entität oder durch Implementieren der Spezifikations- und Benachrichtigungsmuster zum Erzwingen von Validierungsregeln.

Es kann sinnvoll sein, Datenanmerkungen auf der Anwendungsebene in ViewModel-Klassen (anstelle von Domänenentitäten) zu verwenden, die Eingaben akzeptieren, um die Modellüberprüfung innerhalb der UI-Ebene zu ermöglichen. Dies sollte jedoch nicht unter Ausschluss der Überprüfung innerhalb des Domänenmodells erfolgen.

Überprüfen von Entitäten durch Implementieren des Spezifikationsmusters und des Benachrichtigungsmusters

Schließlich besteht ein aufwändigerer Ansatz zur Implementierung von Validierungen im Domänenmodell darin, das Spezifikationsmuster in Verbindung mit dem Benachrichtigungsmuster zu implementieren, wie in einigen der später aufgeführten zusätzlichen Ressourcen erläutert.

Es ist erwähnenswert, dass Sie auch nur eines dieser Muster verwenden können, z. B. das manuelle Überprüfen mit Kontrollanweisungen, während Sie das Benachrichtigungsmuster verwenden, um eine Liste von Überprüfungsfehlern zu sammeln und zurückzugeben.

Verwenden Sie verzögerte Überprüfung in der Domäne

Es gibt verschiedene Ansätze zum Umgang mit verzögerten Validierungen in der Domäne. In seinem Buch Implementing Domain-Driven Design erläutert Vaughn Vernon diese im Abschnitt zur Validierung.

Überprüfung in zwei Schritten

Erwägen Sie auch die Überprüfung in zwei Schritten. Verwenden Sie die Überprüfung auf Feldebene für Ihre Befehlsdatenübertragungsobjekte (Data Transfer Objects, DTOs) und die Überprüfung auf Domänenebene innerhalb Ihrer Entitäten. Sie können dies tun, indem Sie anstelle von Ausnahmen ein Ergebnisobjekt zurückgeben, um die Behandlung der Überprüfungsfehler zu vereinfachen.

Durch die Verwendung von Feldüberprüfung mittels Datenanmerkungen, zum Beispiel, duplizieren Sie die Gültigkeitsprüfungsdefinition nicht. Die Ausführung kann jedoch sowohl serverseitig als auch clientseitig im Fall von DTOs (z. B. Befehle und ViewModels) sein.

Weitere Ressourcen