it-swarm-eu.dev

Wie kann ich bei all diesen Diensten nicht anämisch sein?

Wo ziehen wir die Grenze zwischen Delegation und Kapselung von Geschäftslogik? Es scheint mir, dass je mehr wir delegieren, desto mehr anämisch wir werden. Die Delegierung fördert jedoch auch die Wiederverwendung und das Prinzip DRY Principal). Was ist also für die Delegierung geeignet und was sollte in unseren Domänenmodellen verbleiben?

Nehmen Sie die folgenden Bedenken als Beispiele:

Autorisierung . Sollte das Domänenobjekt für die Aufrechterhaltung seiner Zugriffssteuerungsregeln verantwortlich sein (z. B. eine CanEdit-Eigenschaft) oder sollte dies an eine andere Komponente/einen anderen Dienst delegiert werden, die/der allein für die Verwaltung des Zugriffs verantwortlich ist, z. IAuthorizationService.CanEdit (Objekt)? Oder sollte es eine Kombination aus beiden sein? Vielleicht hat das Domänenobjekt eine CanEdit-Eigenschaft, die an einen internen IAuthorizationService delegiert, um die eigentliche Arbeit auszuführen?

Validierung . Die gleiche Diskussion wie oben bezieht sich auf die Validierung. Wer hält die Regeln ein und wer ist für deren Bewertung verantwortlich? Einerseits sollte der Status des Objekts zu diesem Objekt gehören, und die Gültigkeit ist ein Status, aber wir möchten den Code, der zum Auswerten von Regeln für jedes Domänenobjekt verwendet wird, nicht neu schreiben. Wir könnten Vererbung in diesem Fall verwenden ...

Objekterstellung . Factory-Klasse versus Factory-Methoden versus 'Newing' einer Instanz. Wenn wir eine separate Factory-Klasse verwenden, können wir die Erstellungslogik isolieren und kapseln, jedoch auf Kosten des Öffnens des Objektstatus für die Factory. Dies kann verwaltet werden, wenn sich unsere Domänenschicht in einer separaten Assembly befindet, indem ein von der Factory verwendeter interner Konstruktor verfügbar gemacht wird. Dies wird jedoch zu einem Problem, wenn mehrere Erstellungsmuster vorhanden sind. Und wenn die Fabrik nur den richtigen Konstruktor anruft, was bringt es dann, die Fabrik zu haben?

Factory-Methoden für die Klasse beseitigen das Problem beim Öffnen des internen Status des Objekts. Da sie jedoch statisch sind, können wir Abhängigkeiten nicht wie bei einer separaten Factory-Klasse durch Einfügen einer Factory-Schnittstelle aufheben.

Persistenz . Man könnte argumentieren, dass wenn unser Domain-Objekt CanEdit verfügbar macht, während die Verantwortung für die Durchführung der Autorisierungsprüfung an eine andere Partei (IAuthorizationService) delegiert wird, warum nicht eine Save-Methode für unser Domain-Objekt vorhanden ist, die dasselbe tut? Dies würde es uns ermöglichen, den internen Zustand des Objekts zu bewerten, um festzustellen, ob die Operation ausgeführt werden kann, ohne die Kapselung zu unterbrechen. Natürlich müssen wir die Repository-Instanz in unser Domänenobjekt einfügen, was für mich ein bisschen riecht. Lösen wir stattdessen ein Domänenereignis aus und erlauben einem Handler, die Persistenzoperation auszuführen?

Sehen Sie, wohin ich damit gehe?

Rockford Lhotka hat eine großartige Diskussion über seine Gründe, warum er für sein CSLA-Framework den Weg der verantwortlichen Klasse eingeschlagen hat, und ich habe ein wenig Geschichte mit diesem Framework und kann seine Vorstellung von Geschäftsobjekten, die Domänenobjekten entsprechen, auf vielfältige Weise sehen. Aber wenn ich versuche, mich besser an gute DDD-Ideale zu halten, frage ich mich, wann die Zusammenarbeit zu viel wird.

Was bleibt übrig, wenn ich einen IAuthorizationService, IValidator, IFactory und IRepository für meine aggregierte Wurzel habe? Ist eine Publish-Methode, die den Status des Objekts von Draft in Published ändert, ausreichend, um die Klasse als nicht anämisches Domänenobjekt zu betrachten?

Ihre Gedanken?

92
SonOfPirate

Der größte Teil der Verwirrung scheint sich auf Funktionen zu beziehen, die im Domänenmodell überhaupt nicht vorhanden sein sollten:

  • Persistenz sollte niemals im Domänenmodell sein. Niemals. Aus diesem Grund verlassen Sie sich auf abstrakte Typen wie IRepository, wenn ein Teil des Modells jemals einen anderen Teil des Modells abrufen und die Implementierung mithilfe der Abhängigkeitsinjektion oder einer ähnlichen Technik verkabeln muss. Also streichen Sie das aus der Akte.

  • Autorisierung ist im Allgemeinen nicht Teil Ihres Domain-Modells, es sei denn, es ist tatsächlich Teil der Domain, z. wenn Sie Sicherheitssoftware schreiben. Die Mechanismen, wer was in einer Anwendung ausführen darf, werden normalerweise am "Rand" der Geschäfts-/Domänenebene behandelt, die öffentlichen Teile, mit denen die UI- und Integrationsteile tatsächlich sprechen dürfen - der Controller in MVC, die Services oder das Messaging-System selbst in einer SOA ... Sie erhalten das Bild.

  • Fabriken (und ich nehme an, Sie meinen hier abstrakte Fabriken) sind nicht genau schlecht in einem Domain-Modell zu haben, aber sie sind es fast immer unnötig. Normalerweise haben Sie nur eine Fabrik, in der sich die innere Mechanik der Objekterstellung ändern könnte. Sie haben jedoch nur eine Implementierung des Domänenmodells, was bedeutet, dass es immer nur eine Art von Factory geben wird, die immer dieselben Konstruktoren und anderen Initialisierungscode aufruft.

    Sie können "Convenience" -Fabriken haben, wenn Sie möchten - Klassen, die gängige Kombinationen von Konstruktorparametern usw. enthalten -, aber ehrlich gesagt, wenn in Ihrem Domänenmodell viele Fabriken sitzen, verschwenden Sie nur Zeilen von Code.

Wenn Sie also alle diese Rasen haben, bleibt nur die Validierung. Das ist der einzige, der etwas knifflig ist.

Validierung ist Teil Ihres Domain-Modells, aber auch Teil jeder anderen Komponente der Anwendung. Ihre Benutzeroberfläche und Datenbank haben ihre eigenen, ähnlichen und dennoch unterschiedlichen Validierungsregeln, die auf einem ähnlichen, aber unterschiedlichen konzeptionellen Modell basieren. Es ist nicht wirklich angegeben, ob Objekte eine Validate -Methode benötigen oder nicht, aber selbst wenn dies der Fall ist, delegieren sie diese normalerweise an eine Validator-Klasse (nicht Schnittstelle - Validierung ist nicht abstrakt im Domänenmodell ist es grundlegend).

Beachten Sie, dass der Validator technisch immer noch Teil des Modells ist. Es muss nicht an ein aggregiertes Stammverzeichnis angehängt werden, da es keine Daten oder Status enthält. Domänenmodelle sind konzeptionelle Dinge, die normalerweise physisch in eine Assembly oder eine Sammlung von Assemblys übersetzt werden. Machen Sie sich keine Sorgen um das "anämische" Problem, wenn sich Ihr Delegierungscode in unmittelbarer Nähe des Objektmodells befindet. es zählt immer noch.

Worauf es wirklich ankommt, ist, dass Sie, wenn Sie DDD ausführen möchten, verstehen müssen, was die Domain ist . Wenn Sie immer noch über Dinge wie Persistenz und Autorisierung sprechen, sind Sie auf dem falschen Weg. Die Domäne repräsentiert den Betriebszustand eines Systems - die physischen und konzeptuellen Objekte und Attribute. Alles, was für die Objekte und Beziehungen selbst nicht direkt relevant ist, gehört nicht zum Domänenmodell Punkt.

Als Faustregel gilt, wenn Sie überlegen, ob etwas zum Domain-Modell gehört oder nicht, folgende Frage:

"Kann sich diese Funktionalität jemals aus rein technischen Gründen ändern?" Mit anderen Worten, nicht aufgrund einer beobachtbaren Änderung des realen Geschäfts oder der realen Domäne?

Wenn die Antwort "Ja" lautet, gehört sie nicht zum Domänenmodell. Es ist nicht Teil der Domain.

Es besteht eine sehr gute Chance, dass Sie eines Tages Ihre Persistenz- und Autorisierungsinfrastruktur ändern. Daher sind sie nicht Teil der Domäne, sondern Teil der Anwendung. Dies gilt auch für Algorithmen wie Sortieren und Suchen. Sie sollten keine Implementierung eines binären Suchcodes in Ihr Domain-Modell verschieben, da sich Ihre Domain nur mit dem abstrakten Konzept einer Suche befasst, nicht mit der Funktionsweise.

Wenn Sie, nachdem Sie alle wichtigen Dinge entfernt haben, feststellen, dass das Domänenmodell wirklich anämisch ist, sollte dies als dienen Ein ziemlich guter Hinweis darauf, dass DDD einfach das falsche Paradigma für Ihr Projekt ist.

Einige Domänen sind wirklich anämisch. Social-Bookmarking-Apps haben nicht wirklich viel von einer "Domain", von der man sprechen kann. Alle Ihre Objekte sind im Grunde nur Daten ohne Funktionalität. Ein Vertriebs- und CRM-System hat dagegen eine ziemlich schwere Domäne. Wenn Sie eine Rate -Entität laden, besteht eine vernünftige Erwartung, dass Sie tatsächlich Dinge mit dieser Rate ausführen können, z. B. anwenden zu einer Bestellmenge und lassen Sie es die Mengenrabatte und Promo-Codes und all das lustige Zeug herausfinden.

Domänenobjekte, die normalerweise nur Daten enthalten , bedeuten , dass Sie ein anämisches Domänenmodell haben, dies ist jedoch nicht unbedingt bedeutet, dass Sie ein schlechtes Design erstellt haben. Dies kann bedeuten, dass die Domäne selbst anämisch ist und Sie eine andere Methode verwenden sollten.

67
Aaronaught

Genehmigung. Sollte das Domänenobjekt für die Aufrechterhaltung seiner Zugriffssteuerungsregeln verantwortlich sein

Die Autorisierung ist ein Anliegen für sich. Befehle, die aufgrund fehlender Berechtigungen nicht gültig sind, sollten so früh wie möglich vor der Domäne abgelehnt werden. Dies bedeutet, dass wir häufig sogar die Autorisierung eines potenziellen Befehls überprüfen möchten, um ihn zu erstellen die Benutzeroberfläche (um dem Benutzer nicht einmal die Möglichkeit zum Bearbeiten zu zeigen).

Das Teilen von Autorisierungsstrategien über mehrere Ebenen hinweg (in der Benutzeroberfläche und weiter oben in einem Dienst- oder Befehlshandler) ist einfacher, wenn die Autorisierung getrennt vom Domänenmodell komponiert wird.

Ein schwieriger Teil, der auftreten kann, ist die Kontextautorisierung, bei der ein Befehl nicht nur basierend auf Benutzerrollen, sondern auch auf Geschäftsdaten/-regeln zulässig sein kann oder nicht.

Validierung. Die gleiche Diskussion wie oben bezieht sich auf die Validierung.

Ich würde auch nein sagen, nicht in der Domäne (meistens). Die Validierung erfolgt in verschiedenen Kontexten, und die Validierungsregeln unterscheiden sich häufig zwischen den Kontexten. Es gibt selten ein einfaches, absolutes Gefühl von Gültigkeit oder Ungültigkeit, wenn man die von einem Aggregat eingekapselten Daten betrachtet.

Ebenso wie die Autorisierung verwenden wir die Validierungslogik über mehrere Ebenen hinweg - in der Benutzeroberfläche, im Service- oder Befehlshandler usw. Auch hier ist es einfacher, DRY mit Validierung zu verwenden, wenn es sich um eine separate Komponente handelt Aus praktischer Sicht erfordert die Validierung (insbesondere bei Verwendung von Frameworks) die Bereitstellung von Daten, die ansonsten gekapselt werden sollten, und häufig müssen benutzerdefinierte Attribute an Felder und Eigenschaften angehängt werden. Ich bevorzuge diese in anderen Klassen als meinen Domänenmodellen.

Ich würde lieber einige Eigenschaften in einigen ähnlichen Klassen duplizieren, als zu versuchen, die Anforderungen für ein Validierungsframework in meine Entitäten zu zwingen. Das führt zwangsläufig dazu, dass die Entitätsklassen durcheinander gebracht werden.

Objekterstellung. Factory-Klasse versus Factory-Methoden versus 'Newing' einer Instanz.

Ich benutze eine Indirektionsebene. In meinen neueren Projekten ist dies ein Befehl + Handler zum Erstellen von etwas, d. H. CreateNewAccountCommand. Eine Alternative könnte darin bestehen, immer eine Factory zu verwenden (obwohl dies umständlich sein kann, wenn der Rest einer Entitätsoperation von einer Serviceklasse verfügbar gemacht wird, die von der Factory-Klasse getrennt ist).

Im Allgemeinen versuche ich jedoch, bei der Auswahl des Designs für die Objekterstellung flexibler zu sein. new ist einfach und vertraut, aber nicht immer ausreichend. Ich denke, dass es wichtig ist, hier ein Urteil zu fällen und es verschiedenen Teilen eines Systems zu ermöglichen, je nach Bedarf unterschiedliche Strategien anzuwenden.

Beharrlichkeit. ... warum nicht eine Save-Methode für unser Domain-Objekt haben?

Dies ist selten eine gute Idee; Ich denke, es gibt viele gemeinsame Erfahrungen, um dies zu unterstützen.

Was bleibt übrig, wenn ich einen IAuthorizationService, IValidator, IFactory und IRepository für meine aggregierte Wurzel habe? Ist eine Publish-Methode, die den Status des Objekts von Draft in Published ändert, ausreichend, um die Klasse als nicht anämisches Domänenobjekt zu betrachten?

Möglicherweise ist ein Domain-Modell für diesen Teil Ihrer Anwendung nicht die richtige Wahl.

6
quentin-starin

OK, hier geht für mich. Ich werde dies verhindern, indem ich Folgendes sage:

  • Vorzeitige Optimierung (und dazu gehört auch das Design) kann häufig Probleme verursachen.

  • IANMF (ich bin nicht Martin Fowler);)

  • Ein schmutziges kleines Geheimnis ist, dass bei kleinen Projekten (auch bei mittelgroßen) die Konsistenz Ihres Ansatzes eine Rolle spielt.

Autorisierung

Authentifizierung und Autorisierung sind für mich immer ein Querschnittsthema. In meiner glücklichen kleinen Java Welt wird dies an Spring Security oder das Apache Shiro-Framework delegiert.

Validierung Für mich ist die Validierung Teil des Objekts, da ich sehe, dass es definiert, was das Objekt ist.

z.B. Ein Autoobjekt hat 4 Räder (OK, es gibt einige seltsame Ausnahmen, aber lassen Sie uns das seltsame dreirädrige Auto vorerst ignorieren). Ein Auto ist nur gültig, wenn es 4 hat (in meiner Welt), so dass die Validierung Teil der Definition eines Autos ist. Das bedeutet nicht, dass Sie keine Hilfsvalidierungsklassen haben können.

In meiner glücklichen Java Welt verwende ich Bean-Validierungs-Frameworks und verwende einfache Anmerkungen für die meisten meiner Bean-Felder. Es ist dann einfach, Ihr Objekt zu validieren, egal in welcher Ebene Sie sich befinden.

Objekterstellung

Ich betrachte Factory-Klassen mit Vorsicht. Zu oft habe ich die Klasse xyxFactoryFactory gesehen;)

Ich neige dazu, bei Bedarf nur ein new -Objekt zu erstellen, bis ich auf einen Fall stoße, in dem die Abhängigkeitsinjektion gerechtfertigt ist (und da ich versuche, einem TDD-Ansatz zu folgen, tritt dies häufig auf).

In meiner glücklichen Java Welt ist das zunehmend Guice, aber der Frühling ist immer noch der König hier.

Ausdauer

Das ist also eine Debatte, die in Kreisen und Kreisverkehren stattfindet, und ich habe immer zwei Gedanken darüber.

Einige sagen, wenn man das Objekt „rein“ betrachtet, ist Persistenz keine Kerneigenschaft, sondern lediglich ein äußeres Anliegen.

Andere sind der Ansicht, dass Ihre Domänenobjekte implizit eine "persistable" Schnittstelle implementieren (ja, ich weiß, dass ich mich hier strecke). Daher ist es in Ordnung, die verschiedenen Methoden save, delete usw. zu verwenden. Dies wird als pragmatischer Ansatz angesehen und viele ORM-Technologien (JPA in meiner glücklichen Java Welt) behandeln Objekte auf diese Weise.

Aus Sicherheitsgründen stelle ich sicher, dass die Berechtigungen zum Bearbeiten/Löschen/Hinzufügen/Was auch immer für den Dienst korrekt festgelegt sind, der die Methode zum Speichern/Aktualisieren/Löschen für das Objekt aufruft. Wenn ich wirklich paranoid bin, kann ich sogar die Berechtigungen für das Domänenobjekt selbst festlegen.

HTH!

4
Martijn Verburg

Jimmy Nilsson geht in seinem Buch über DDD auf dieses Thema ein. Er begann mit einem anämischen Modell, ging in einem späteren Projekt zu nicht anämischen Modellen und entschied sich schließlich für anämische Modelle. Seine Argumentation war, dass die anämischen Modelle in mehreren Diensten mit unterschiedlicher Geschäftslogik wiederverwendet werden könnten.

Der Kompromiss ist die mangelnde Entdeckbarkeit. Die Methoden, mit denen Sie Ihre anämischen Modelle bearbeiten können, sind auf eine Reihe von Diensten verteilt, die sich an anderer Stelle befinden.

2
Todd Smith

Diese Frage wurde vor langer Zeit gestellt, ist aber mit Domain Driven Design gekennzeichnet. Ich denke, die Frage selbst enthält ein grundlegendes Missverständnis der gesamten Praxis, und die Antworten, einschließlich der akzeptierten Antwort, setzen ein grundlegendes Missverständnis fort.

In einer DDD-Architektur gibt es kein "Domänenmodell".

Nehmen wir als Beispiel die Autorisierung. Lassen Sie mich über eine Frage nachdenken: Stellen Sie sich vor, zwei verschiedene Benutzer authentifizieren sich bei Ihrem System. Ein Benutzer hat die Berechtigung, eine bestimmte Entität zu ändern, der andere jedoch nicht. Warum nicht?

Ich hasse einfache und erfundene Beispiele, weil sie oft mehr verwirren als aufklären. Aber tun wir so, als hätten wir zwei verschiedene Domänen. Erstens ist eine CMS-Plattform für eine Marketingagentur. Diese Agentur hat viele Kunden, die alle Inhalte online haben, die von Textern und Grafikern verwaltet werden müssen. Der Inhalt umfasst Blog-Beiträge sowie Zielseiten für verschiedene Kunden.

Die andere Domäne ist die Bestandsverwaltung für ein Schuhunternehmen. Das System verwaltet den Lagerbestand von der Ankunft des Herstellers in Frankreich über Vertriebszentren in den kontinentalen USA bis hin zu Einzelhandelsgeschäften auf lokalen Märkten und schließlich zu dem Kunden, der die Schuhe im Einzelhandel kauft.

Wenn Sie der Meinung sind, dass die Autorisierungsregeln für beide Unternehmen gleich sind, ist dies ein guter Kandidat für einen Dienst außerhalb der Domäne. Ich bezweifle jedoch, dass die Autorisierungsregeln dieselben sind. Sogar die Konzepte hinter den Benutzern wären unterschiedlich. Sicher wäre die Sprache anders. Die Marketingagentur hat wahrscheinlich Rollen wie Postautor und Asset-Eigentümer, während die Schuhfirma wahrscheinlich Rollen wie Versandkaufmann, Lagerverwalter oder Geschäftsleiter hat.

Diesen Konzepten sind wahrscheinlich alle Arten von Berechtigungsregeln zugeordnet, die in der Domäne modelliert werden müssen. Das heißt aber nicht, dass sie alle Teil desselben Modells sind, auch innerhalb derselben App. Denken Sie daran, dass es verschiedene begrenzte Kontexte gibt.

Vielleicht könnte man ein nicht anämisches Domain-Modell im Rahmen der Autorisierung als anders betrachten als das Weiterleiten von Schuhsendungen an Geschäfte mit geringem Lagerbestand oder das Weiterleiten von Site-Besuchern an die entsprechende Zielseite, je nachdem, auf welche Anzeige sie geklickt haben.

Wenn Sie mit anämischen Domänenmodellen konfrontiert sind, müssen Sie möglicherweise einfach mehr Zeit für die Kontextzuordnung aufwenden, bevor Sie mit dem Schreiben von Code beginnen.

2
RibaldEddie