Ich entscheide, ob ich ein Rich-Domain-Modell anstelle eines anämischen Domain-Modells verwenden soll, und suche nach guten Beispielen für beide.
Ich habe Webanwendungen mithilfe eines anämischen Domänenmodells erstellt , das von einem Service -> Repository -> Speicherschichtsystem unterstützt wird, FluentValidation für die BL-Validierung verwendet und alle meine BL in die Service-Schicht eingefügt .
Ich habe Eric Evans DDD-Buch gelesen und er (zusammen mit Fowler und anderen) scheint zu glauben, dass anämische Domänenmodelle ein Anti-Muster sind.
Ich wollte nur wirklich einen Einblick in dieses Problem bekommen.
Außerdem suche ich wirklich nach einigen guten (grundlegenden) Beispielen für ein Rich-Domain-Modell und den Vorteilen gegenüber dem anämischen Domain-Modell, das es bietet.
Antworten:
Der Unterschied besteht darin, dass ein anämisches Modell Logik von Daten trennt. Die Logik wird häufig in Klassen eingeteilt genannt
**Service
,**Util
,**Manager
,**Helper
und so weiter. Diese Klassen implementieren die Dateninterpretationslogik und nehmen daher das Datenmodell als Argument. Z.BDer Rich-Domain-Ansatz kehrt dies um, indem die Dateninterpretationslogik in das Rich-Domain-Modell eingefügt wird. Auf diese Weise werden Logik und Daten zusammengefügt, und ein umfangreiches Domänenmodell würde folgendermaßen aussehen:
Dies hat einen großen Einfluss auf die Objektkonsistenz. Da die Dateninterpretationslogik die Daten umschließt (auf Daten kann nur über Objektmethoden zugegriffen werden), können die Methoden auf Zustandsänderungen anderer Daten reagieren -> Dies nennen wir Verhalten.
In einem anämischen Modell können die Datenmodelle nicht garantieren, dass sie sich in einem Rechtszustand befinden, während dies in einem Rich-Domain-Modell möglich ist. Ein reichhaltiges Domänenmodell wendet OO-Prinzipien wie Kapselung, Verstecken von Informationen und Zusammenführen von Daten und Logik an. Daher ist ein anämisches Modell aus OO-Sicht ein Anti-Muster.
Weitere Informationen finden Sie in meinem Blog unter https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/.
quelle
Bozhidar Bozhanov scheint in diesem Blog-Beitrag für das anämische Modell zu argumentieren .
Hier ist die Zusammenfassung, die er präsentiert:
Domänenobjekte sollten nicht im Frühjahr (IoC) verwaltet werden, sie sollten keine DAOs oder irgendetwas im Zusammenhang mit der Infrastruktur enthalten
Domänenobjekte haben die Domänenobjekte, von denen sie abhängen, durch den Ruhezustand (oder den Persistenzmechanismus) festgelegt.
Domänenobjekte führen die Geschäftslogik aus, wie es die Kernidee von DDD ist, dies schließt jedoch keine Datenbankabfragen oder CRUD-Operationen für den internen Status des Objekts ein
DTOs werden selten benötigt - die Domänenobjekte sind in den meisten Fällen die DTOs selbst (wodurch etwas Boilerplate-Code gespeichert wird).
Dienste führen CRUD-Operationen aus, senden E-Mails, koordinieren die Domänenobjekte, generieren Berichte basierend auf mehreren Domänenobjekten, führen Abfragen aus usw.
Die Service- (Anwendungs-) Schicht ist nicht so dünn, enthält jedoch keine Geschäftsregeln, die den Domänenobjekten eigen sind
Codegenerierung sollte vermieden werden. Abstraktion, Entwurfsmuster und DI sollten verwendet werden, um die Notwendigkeit der Codegenerierung zu überwinden und letztendlich die Codeduplizierung zu beseitigen.
AKTUALISIEREN
Ich habe kürzlich diesen Artikel gelesen, in dem der Autor befürwortet, einen hybriden Ansatz zu verfolgen - Domänenobjekte können verschiedene Fragen nur aufgrund ihres Zustands beantworten (was im Fall von vollständig anämischen Modellen wahrscheinlich in der Serviceschicht geschehen würde).
quelle
Mein Standpunkt ist folgender:
Anämisches Domänenmodell = Datenbanktabellen, die Objekten zugeordnet sind (nur Feldwerte, kein reales Verhalten)
Rich Domain Model = Eine Sammlung von Objekten, die das Verhalten offenlegen
Wenn Sie eine einfache CRUD-Anwendung erstellen möchten, reicht möglicherweise ein anämisches Modell mit einem klassischen MVC-Framework aus. Wenn Sie jedoch eine Art Logik implementieren möchten, bedeutet ein anämisches Modell, dass Sie keine objektorientierte Programmierung durchführen.
* Beachten Sie, dass das Objektverhalten nichts mit Persistenz zu tun hat. Eine andere Ebene (Datenmapper, Repositorys usw.) ist für die Persistenz von Domänenobjekten verantwortlich.
quelle
x
,y
,sum
unddifference
. Das sind vier Dinge. Oder Sie könnten argumentieren, es ist Addition und Subtraktion (zwei Dinge). Oder Sie könnten argumentieren, dass es Mathe ist (eine Sache). Es gibt viele Blog-Beiträge darüber, wie Sie bei der Anwendung von SRP ein Gleichgewicht finden. Hier ist eine: hackernoon.com/...Abbildung 1 zeigt ein anämisches Domänenmodell, bei dem es sich im Grunde um ein Schema mit Gettern und Setzern handelt.
In diesem umfassenderen Modell besteht die öffentliche Oberfläche des Kunden aus expliziten Methoden, anstatt nur Eigenschaften zum Lesen und Schreiben bereitzustellen.
quelle
Address
, sondern von diesenExtendedAddress
geerbt wirdAddress
? 2) OderCustomerCreditCard
Konstruktorparameter ändern , umBankID
statt zu nehmenBankName
?Als ich monolithische Desktop-Apps schrieb, erstellte ich umfangreiche Domänenmodelle, mit denen ich Spaß daran hatte, sie zu erstellen.
Jetzt schreibe ich winzige HTTP-Microservices, es gibt so wenig Code wie möglich, einschließlich anämischer DTOs.
Ich denke, DDD und dieses anämische Argument stammen aus der Zeit der monolithischen Desktop- oder Server-Apps. Ich erinnere mich an diese Zeit und würde zustimmen, dass anämische Modelle seltsam sind. Ich habe eine große monolithische FX-Handels-App erstellt und es gab kein Modell, wirklich, es war schrecklich.
Bei Microservices sind die kleinen Services mit ihrem reichhaltigen Verhalten wohl die zusammensetzbaren Modelle und Aggregate innerhalb einer Domäne. Daher erfordern die Microservice-Implementierungen selbst möglicherweise keine weitere DDD. Die Microservice-Anwendung kann die Domäne sein.
Ein Auftragsmikroservice kann nur sehr wenige Funktionen haben, die als RESTful-Ressourcen oder über SOAP oder was auch immer ausgedrückt werden. Der Mikroservice-Code für Bestellungen kann sehr einfach sein.
Ein größerer monolithischerer einzelner (Mikro-) Dienst, insbesondere ein Dienst, der das Modell im RAM hält, kann von DDD profitieren.
quelle
Einer der Vorteile von Rich Domain-Klassen besteht darin, dass Sie ihr Verhalten (Methoden) jedes Mal aufrufen können, wenn Sie den Verweis auf das Objekt in einer beliebigen Ebene haben. Außerdem neigen Sie dazu, kleine und verteilte Methoden zu schreiben, die zusammenarbeiten. In anämischen Domänenklassen neigen Sie dazu, fette Verfahrensmethoden (in der Serviceschicht) zu schreiben, die normalerweise vom Anwendungsfall abhängen. Sie sind im Vergleich zu Rich-Domain-Klassen normalerweise weniger wartbar.
Ein Beispiel für Domänenklassen mit Verhalten:
Die Methode
needToDeliver()
gibt eine Liste der Artikel zurück, die geliefert werden müssen, einschließlich Bonus. Es kann innerhalb der Klasse, von einer anderen verwandten Klasse oder von einer anderen Ebene aufgerufen werden. Wenn Sie beispielsweiseOrder
zur Ansicht übergehen , können Sie mitneedToDeliver()
ausgewähltOrder
die Liste der Elemente anzeigen, die vom Benutzer bestätigt werden sollen, bevor er auf die Schaltfläche Speichern klickt, um die Anzeige beizubehaltenOrder
.Auf Kommentar antworten
So verwende ich die Domänenklasse vom Controller:
Die Erstellung von
Order
und seineLineItem
ist in einer Transaktion. Wenn einer derLineItem
nicht erstellt werden kann, wird keinOrder
erstellt.Ich neige dazu, Methoden zu haben, die eine einzelne Transaktion darstellen, wie zum Beispiel:
Alles, was sich darin befindet,
deliver()
wird als eine einzige Transaktion ausgeführt. Wenn ich viele nicht verwandte Methoden in einer einzigen Transaktion ausführen muss, würde ich eine Serviceklasse erstellen.Um eine verzögerte Ladeausnahme zu vermeiden, verwende ich JPA 2.1 mit dem Namen Entity Graph. Im Bildschirm "Controller für Zustellung" kann ich beispielsweise eine Methode zum Laden von
delivery
Attributen und zum Ignorieren erstellenbonus
, zrepository.findOrderByNumberFetchDelivery()
. Im Bonusbildschirm rufe ich eine andere Methode auf, diebonus
Attribute lädt und ignoriertdelivery
, zrepository.findOrderByNumberFetchBonus()
. Dies erfordert Disziplin, da ich immer noch nichtdeliver()
im Bonusbildschirm anrufen kann .quelle
Ich denke, die Wurzel des Problems liegt in der falschen Zweiteilung. Wie ist es möglich, diese beiden Modelle zu extrahieren: reich und "anämisch" und sie einander gegenüberzustellen? Ich denke, es ist nur möglich, wenn Sie falsche Vorstellungen darüber haben, was eine Klasse ist . Ich bin mir nicht sicher, aber ich glaube, ich habe es in einem von Bozhidar Bozhanov-Videos auf Youtube gefunden. Eine Klasse ist keine Daten + Methode über diese Daten. Es ist ein völlig ungültiges Verständnis, das zur Unterteilung von Klassen in zwei Kategorien führt: nur Daten, also anämisches Modell und Daten + Methoden - so reichhaltiges Modell (genauer gesagt gibt es eine dritte Kategorie: nur Methoden gerade).
Die Wahrheit ist, dass Klasse ein Konzept in einem ontologischen Modell ist, ein Wort, eine Definition, ein Begriff, eine Idee, es ist ein DENOTAT . Und dieses Verständnis beseitigt falsche Dichotomie: Sie können kein NUR anämisches Modell oder NUR ein reichhaltiges Modell haben, da dies bedeutet, dass Ihr Modell nicht angemessen und für die Realität nicht relevant ist: Einige Konzepte enthalten nur Daten, einige nur Methoden, andere von ihnen sind gemischt. Da wir in diesem Fall versuchen, einige Kategorien, Objektmengen, Beziehungen, Konzepte mit Klassen zu beschreiben, und wie wir wissen, sind einige Konzepte nur Prozesse (Methoden), einige nur Attribute (Daten), andere Sie sind Beziehungen zu Attributen (gemischt).
Ich denke, eine adäquate Anwendung sollte alle Arten von Klassen umfassen und vermeiden, sich fanatisch auf nur ein Modell zu beschränken. Egal, wie die Logik dargestellt wird: mit Code oder mit interpretierbaren Datenobjekten (wie Free Monads ): Wir sollten Klassen (Konzepte, Denotate) haben, die Prozesse, Logik, Beziehungen, Attribute, Merkmale, Daten usw. darstellen und nicht zu versuchen, einige von ihnen zu vermeiden oder sie alle auf die eine Art zu reduzieren.
Wir können also Logik in eine andere Klasse extrahieren und Daten in der ursprünglichen Klasse belassen, aber es hat keinen Sinn, da ein Konzept Attribute und Beziehungen / Prozesse / Methoden enthalten kann und eine Trennung von ihnen das Konzept unter zwei Namen dupliziert, die sein können reduziert auf Muster: "OBJECT-Attribute" und "OBJECT-Logic". In prozeduralen und funktionalen Sprachen ist es aufgrund ihrer Einschränkung in Ordnung, aber es ist eine übermäßige Selbstbeherrschung für eine Sprache, mit der Sie alle Arten von Konzepten beschreiben können.
quelle
Anämische Domänenmodelle sind wichtig für ORM und die einfache Übertragung über Netzwerke (das Lebenselixier aller kommerziellen Anwendungen). OO ist jedoch sehr wichtig für die Kapselung und Vereinfachung der Transaktions- / Handhabungsteile Ihres Codes.
Daher ist es wichtig, sich von einer Welt in die andere identifizieren und konvertieren zu können.
Benennen Sie Anemic-Modelle wie AnemicUser oder UserDAO usw., damit Entwickler wissen, dass es eine bessere Klasse gibt, und verwenden Sie dann einen geeigneten Konstruktor für die Klasse None Anemic
und Adaptermethode zum Erstellen der anämischen Klasse für Transport / Persistenz
Verwenden Sie den nicht anämischen Benutzer überall außerhalb von Transport / Persistenz
quelle
Hier ist ein Beispiel, das helfen könnte:
Anämisch
Nicht anämisch
quelle
Der klassische Ansatz für DDD besagt nicht, dass Anemic vs Rich Models um jeden Preis vermieden werden müssen. MDA kann jedoch weiterhin alle DDD-Konzepte (begrenzte Kontexte, Kontextzuordnungen, Wertobjekte usw.) anwenden, jedoch in allen Fällen Anemic vs Rich-Modelle verwenden. Es gibt viele Fälle, in denen die Verwendung von Domänendiensten zum Orchestrieren komplexer Domänennutzungsfälle über eine Reihe von Domänenaggregaten hinweg ein viel besserer Ansatz ist als nur das Aufrufen von Aggregaten aus der Anwendungsschicht. Der einzige Unterschied zum klassischen DDD-Ansatz besteht darin, wo sich alle Validierungen und Geschäftsregeln befinden. Es gibt ein neues Konstrukt, das als Modellvalidatoren bekannt ist. Validatoren stellen die Integrität des vollständigen Eingabemodells sicher, bevor ein Anwendungsfall oder ein Domänenworkflow stattfindet. Die aggregierten Root- und untergeordneten Entitäten sind anämisch, aber jede kann bei Bedarf ihre eigenen Modellvalidatoren aufrufen. durch seinen Root-Validator. Validatoren, die sich immer noch an SRP halten, sind leicht zu warten und können in Einheiten überprüft werden.
Der Grund für diese Verschiebung ist, dass wir uns jetzt mehr auf eine API als auf einen UX-Ansatz für Microservices konzentrieren. REST hat dabei eine sehr wichtige Rolle gespielt. Der traditionelle API-Ansatz (aufgrund von SOAP) wurde ursprünglich auf eine befehlsbasierte API im Vergleich zu HTTP-Verben (POST, PUT, PATCH, GET und DELETE) festgelegt. Eine befehlsbasierte API passt gut zum objektorientierten Ansatz von Rich Model und ist immer noch sehr gültig. Einfache CRUD-basierte APIs eignen sich zwar viel besser in ein Rich-Modell, eignen sich jedoch viel besser für einfache anämische Modelle, Validatoren und Domänendienste, um den Rest zu koordinieren.
Ich liebe DDD in allem, was es zu bieten hat, aber irgendwann muss man es ein wenig dehnen, um sich an die sich ständig ändernde und bessere Herangehensweise an die Architektur anzupassen.
quelle