Null-Argument-Konstruktoren und immer gültige Entitäten


8

Ich habe in letzter Zeit viel über immer gültige Domänenentitäten gelesen. Ich bin zu der Überzeugung gelangt, dass ich Folgendes tun muss, um sicherzustellen, dass die Entitäten immer gültig sind:

1) Entfernen Sie die primitive Besessenheit und fügen Sie Validierungs- / Domänenregeln in die Wertobjektkonstruktoren ein, wie hier erläutert: https://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/ . 2) Fügen Sie Validierungs- / Domänenregeln in den Konstruktor von Entitäten oder die Eigenschaftssetzer ein, wie hier erläutert: http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design-ddd/ .

Ich schaue mir dann jedoch einige Open Source-Projekte wie dieses an: https://github.com/gregoryyoung/mr . Soweit ich weiß, ist der Autor dieses Projekts ein Verfechter des immer gültigen Domänenmodells, und dennoch schaue ich hier in die InventoryItem-Klasse: https://github.com/gregoryyoung/mr/blob/master/SimpleCQRS/Domain.cs . Ich stelle fest, dass ich dazu in der Lage bin:

InventoryItem inventoryItem = new InventoryItem();

oder dieses:

InventoryItem inventoryItem2 = new InventoryItem(Guid.Empty,null);

In meinen Augen bedeutet dies, dass die Entität in einem ungültigen Zustand initialisiert wird. Dies scheint auch bei allen anderen Open Source-Projekten der Fall zu sein, die ich mir kürzlich angesehen habe, z. B. diesem: https://github.com/dcomartin/DDD-CQRS-ES-Example/blob/master/src/Domain /Customer.cs .

Mir ist klar, dass dieses Open Source-Projekt eine Kontextvalidierung enthält ( https://martinfowler.com/bliki/ContextualValidation.html ). Mir ist auch klar, dass ORMs einen leeren Standardkonstruktor benötigen, wenn sie dem Domänenmodell zugeordnet sind.

Befindet sich ein Domänenobjekt in einem gültigen Zustand, wenn es mit Standardwerten unter Verwendung eines Konstruktors für Nullargumente initialisiert / mit leeren / Nullwerten initialisiert wurde?


2
Sie können wirklich nicht verallgemeinern. Es könnte sein. Es könnte nicht sein. Ist das wichtig? IMHO nein, besonders frühe DDD-Gespräche haben vergessen zu denken, dass die Realität anders ist, und zum Beispiel diktieren ORMs / Sprachen / Frameworks einige Regeln (und der Preis, um sie zu umgehen, kann zu hoch oder nicht würdig sein). Ok, auch das von Ihnen aufgelistete Projekt scheint nicht der bestmögliche .NET-Code zu sein ...
Adriano Repetti

@Adriano Repetti, kennen Sie gute Open-Source-Projekte von cqrs? Fügen Sie eine Validierung in Ihre Befehle ein?
w0051977

2
Ich würde die Parameterüberprüfung von der Domänenüberprüfung unterscheiden (falls zutreffend). Ein Nullparameter hat beispielsweise nichts mit der Domäne zu tun. Er stammt aus einem Implementierungsdetail (der von Ihnen verwendeten Sprache). Ersteres mache ich immer, egal was und wo. Die Domänenvalidierung ist schwieriger, aber wenn Sie sich wirklich wirklich Sorgen über gebrochene Invarianten machen, sollten Sie, IMO, unveränderliche Objekte in Betracht ziehen . Sie sind (wieder nur meiner Meinung nach) ein oft besserer Kompromiss als ein heiliger Krieg gegen Sprachen, die wir wirklich benutzen (und das war eine Kritik an vielen älteren DDD-Takes)
Adriano Repetti

Antworten:


7

Bevor ich mich mit den Überlegungen befasse, die bei der Objekterstellung anfallen, wollen wir zunächst die Motivation hinter Ihrer Frage ansprechen: den Ausdruck "Immer gültige Domänenentitäten". Dieser Satz ist bestenfalls irreführend und macht im Zusammenhang mit DDD wenig Sinn. Dies sollte aus zwei verwandten Gründen offensichtlich sein:

Das erste ist, dass es Ihren Fokus implizit vom Verhalten des Systems weg verlagert und Sie stattdessen auffordert, die Validierung nur in Bezug auf den Status zu berücksichtigen. Oberflächlich betrachtet mag dies sinnvoll erscheinen (die Validierung erfolgt natürlich in Bezug auf den Zustand!), Aber Sie müssen sich daran erinnern, dass das Grundprinzip von DDD darin besteht, dass ein System gemäß dem Verhalten modelliert wird . Die Motivation dafür ist ganz einfach, dass der Kontext oder der Geschäftsprozess selbst oft eine wichtige Überlegung ist, wenn festgestellt wird, ob ein Teil des Staates gültig ist oder nicht. Das Modellieren eines Systems auf diese Weise kann seine Komplexität erheblich reduzieren.

Dies bringt uns zum zweiten Grund, der sich auf die praktischen Anforderungen bezieht, die ein solches System mit sich bringen würde. Um ein System von "Immer gültigen Domänenentitäten" zu erstellen, müsste jede einzelne Statuspermutation gemäß den Geschäftsprozessen modelliert werden, in denen der Status verwendet wird. Ein einfaches Beispiel kann die Einschränkungen veranschaulichen:

Regeln:

  • Customer muss über 18 sein, um sich zu registrieren
  • Customer muss unter 25 sein, um einen Rabatt bei der Registrierung zu erhalten
  • Customer muss über 25 sein, um eine Reservierung vorzunehmen

Das erste, was Sie beachten sollten, ist, dass alle diese Regeln (wie fast alle Regeln) für einige Geschäftsprozesse gelten. Sie existieren nicht im luftleeren Raum. Diese Regeln würden am customer.Register()und validiert customer.Reserve(). Dies führt zu einem viel aussagekräftigeren und deklarativeren Paradigma, da klar ist, wo Regeln ausgeführt werden.

Wenn wir diese Regeln so modellieren möchten, dass sie zu einem System von "Immer gültigen Domänenentitäten" führen, müssten wir unsere Customerin Registrarund ReserverEntitäten partitionieren . Und obwohl dies für dieses Beispiel nicht so schlecht erscheint, werden Sie mit zunehmender Komplexität und Fülle von Regeln Explosionsklassen wie diese erhalten, die den Zustand "innerhalb" eines bestimmten Kontexts oder Prozesses darstellen. Dies ist einfach unnötig und führt unweigerlich zu Problemen, wenn zwei solcher Objekte von demselben Statusabschnitt abhängig sind.

Darüber hinaus ist so etwas wie Customer c = new Customer()ein schlechter Ort, um eine Ausnahme auszulösen, da unklar ist, welche Geschäftsregeln gelten könnten. Das bringt uns zu unserer abschließenden Diskussion.

Ich werde nur herauskommen und so weit gehen zu sagen, dass es keine Validierung von Geschäftsregeln geben sollte, die in Konstruktoren stattfinden. Die Objektkonstruktion hat überhaupt nichts mit Ihrer Geschäftsdomäne zu tun. Aus diesem Grund sollten zusätzlich zu den oben genannten Gründen in Bezug auf Kontext und Kohärenz alle Geschäftsregeln in den Methodenkörpern einer Entität durchgesetzt werden (wahrscheinlich sind die Methoden nach dem richtigen Geschäftsprozess benannt ?).

Darüber hinaus ist das "Neuerstellen" eines Objekts nicht dasselbe wie das Erstellen einer neuen Entität in Ihrer Domäne. Objekte kommen nicht aus dem Nichts. Wenn es Geschäftsregeln gibt, wie eine neue Entität in Ihr System gelangen kann, sollte sie in Ihrer Domäne modelliert werden . Hier ist eine weitere Diskussion zu diesem Thema durch einen wahren Meister http://udidahan.com/2009/06/29/dont-create-aggregate-roots/


3

Befindet sich ein Domänenobjekt in einem gültigen Zustand, wenn es mit Standardwerten unter Verwendung eines Konstruktors für Nullargumente initialisiert / mit leeren / Nullwerten initialisiert wurde?

Es kann sein.

Es gibt nichts gegen die Regeln zum Erstellen eines gültigen Domänenobjekts mit einem Standardkonstruktor.

Es gibt nichts gegen die Regeln zum Erstellen eines gültigen Domänenobjekts mit leeren Werten.

Es gibt nichts gegen die Regeln zum Erstellen eines gültigen Domänenobjekts, dem optionale Elemente fehlen.

Es gibt nichts gegen die Regeln zum Erstellen eines gültigen Domänenobjekts mit Nullen.

Wo Probleme auftreten: Erstellen von Domänenobjekten, die ihrer eigenen semantischen Algebra nicht entsprechen.

Die korrekte Implementierung hängt von einer korrekten Interpretation der Semantik ab.

Es ist normal, ein Domänenkonzept verzeihend darzustellen und schrittweise zusätzliche Einschränkungen anzuwenden. Dies ist einer der Bereiche, in denen Implementierungssprachen, die das Hinzufügen von Typen (z. B. F #) vereinfachen, Vorteile gegenüber ungeschickteren Sprachen wie Java haben.

Wenn wir beispielsweise eine Domäne haben, die sich um Zahlen kümmert, und innerhalb dieser Domäne einige Funktionen vorhanden sind, die für Primzahlen oder Mersenne-Primzahlen spezifisch sind, besteht ein natürlicher Mechanismus darin, drei verschiedene Typen zu erstellen . Machen Sie deutlich, dass auf die Eingaben in verschiedenen Teilen Ihrer Lösung unterschiedliche Einschränkungen angewendet werden müssen.

Number n = new Number(...)
Optional<Prime> p = n.prime()
if (p.isPresent()) {
    Optional<MersennePrime> mp = p.mersennePrime()
    // ...
} 

Bei dieser Art von Design würde die "Validierung", dass die Zahl wirklich ein Prime ist, innerhalb des Prime-Konstruktors selbst existieren. In einem Kontext, in dem wir die zusätzliche Einschränkung benötigen, dass der Parameter eine Mersenne- Primzahl ist, verwenden wir eine Prime-> MersennePrime-Konvertierung, um sicherzustellen, dass das Wissen über die Mersenne-Einschränkung eine Berechtigung hat ("Wiederholen Sie sich nicht").

"Machen Sie das implizite, explizite"


Vielen Dank. Können Sie anhand eines Beispiels erklären, was semantische Algebra ist? Könnten Sie auch erklären, wie das Codebeispiel relevant ist? +1 für "da ist nichts dagegen ....".
w0051977

Sicherlich ist eine Entität ohne leere ID eine schlechte Sache? Es sei denn, es gibt Szenarien, in denen Sie die Domänenklasse mit Standardwerten initialisieren und dann schrittweise erstellen?
w0051977
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.