Die meisten taktischen DDD-Entwurfsmuster gehören zum objektorientierten Paradigma, und ein anämisches Modell beschreibt die Situation, in der die gesamte Geschäftslogik in Services und nicht in Objekte integriert wird, wodurch sie zu einer Art DTO werden. Mit anderen Worten, das anämische Modell ist ein Synonym für den prozeduralen Stil, der für komplexe Modelle nicht empfohlen wird.
Ich bin nicht sehr erfahren in der reinen funktionalen Programmierung, möchte jedoch wissen, wie DDD in das FP-Paradigma passt und ob der Begriff "anämisches Modell" in diesem Fall immer noch existiert.
functional-programming
domain-driven-design
Pavel Voronin
quelle
quelle
Antworten:
Die Art und Weise, wie das Problem des "anämischen Modells" beschrieben wird, lässt sich nicht so gut auf FP übertragen, wie es ist. Zunächst muss es entsprechend verallgemeinert werden. Im Kern ist ein anämisches Modell ein Modell, das Wissen über die ordnungsgemäße Verwendung enthält, das nicht vom Modell selbst eingekapselt wird. Stattdessen wird dieses Wissen auf einen Stapel verwandter Dienste verteilt. Diese Dienste sollten nur Kunden des Modells sein, aber aufgrund ihrer Anämie werden sie dafür verantwortlich gemacht. Angenommen, eine
Account
Klasse kann nicht zum Aktivieren oder Deaktivieren von Konten oder sogar zum Nachschlagen von Informationen zu einem Konto verwendet werden, es sei denn, sie wird über eineAccountManager
Klasse verarbeitet. Das Konto sollte für grundlegende Vorgänge verantwortlich sein, nicht für eine externe Manager-Klasse.Bei der funktionalen Programmierung besteht ein ähnliches Problem, wenn Datentypen nicht genau das darstellen, was sie modellieren sollen. Angenommen, wir müssen einen Typ definieren, der Benutzer-IDs darstellt. Eine "anämische" Definition würde angeben, dass Benutzer-IDs Zeichenfolgen sind. Das ist technisch machbar, stößt aber auf große Probleme, da Benutzer-IDs nicht wie willkürliche Zeichenfolgen verwendet werden. Es macht keinen Sinn, sie zu verketten oder Teilzeichenfolgen daraus herauszuschneiden. Unicode sollte eigentlich keine Rolle spielen und sie sollten leicht in URLs und andere Kontexte mit strengen Zeichen- und Formatbeschränkungen eingebettet werden können.
Die Lösung dieses Problems erfolgt normalerweise in wenigen Schritten. Ein einfacher erster Schnitt lautet: "Nun, a
UserID
wird gleichbedeutend mit einer Zeichenfolge dargestellt, aber es handelt sich um verschiedene Typen, und Sie können keinen verwenden, bei dem Sie den anderen erwarten." Haskell (und einige andere typisierte funktionale Sprachen) bietet diese Funktion übernewtype
:Dies definiert eine
UserID
Funktion, die bei Angabe einesString
Konstrukts einen Wert erzeugt, der vom Typsystem wie ein behandeltUserID
wird, der aber nurString
zur Laufzeit vorliegt. Jetzt können Funktionen deklarieren, dass sie einenUserID
anstelle eines Strings benötigen . Verwenden vonUserID
s, bei denen Sie zuvor Zeichenfolgen verwendet haben, um zu verhindern, dass Code zweiUserID
s zusammenfügt. Das Typensystem garantiert, dass dies nicht passieren kann, es sind keine Tests erforderlich.Die Schwäche dabei ist, dass Code immer noch ein beliebiges
String
Like nehmen"hello"
und daraus ein konstruierenUserID
kann. Weitere Schritte umfassen das Erstellen einer "Smart-Konstruktor" -Funktion, die bei Angabe eines Strings einige Invarianten überprüft und nur dann ein zurückgibt,UserID
wenn sie zufrieden sind. Dann wird der "dumme"UserID
Konstruktor privat gemacht. Wenn ein Client dies wünschtUserID
, muss er den intelligenten Konstruktor verwenden, wodurch verhindert wird, dass fehlerhafte Benutzer-IDs entstehen.In weiteren Schritten wird der
UserID
Datentyp so definiert , dass es nicht möglich ist , einen fehlerhaften oder "unpassenden" Datentyp zu erstellen . So definieren Sie beispielsweise eineUserID
als Liste von Ziffern:Zum Erstellen einer
UserID
Liste müssen Ziffern angegeben werden. In Anbetracht dieser Definition ist es trivial zu zeigen, dass es unmöglich istUserID
, dass ein Element existiert, das nicht in einer URL dargestellt werden kann. Das Definieren solcher Datenmodelle in Haskell wird häufig durch erweiterte Typsystemfunktionen wie Datentypen und Generalized Algebraic Data Types (GADTs) unterstützt , mit denen das Typsystem mehr Invarianten für Ihren Code definieren und nachweisen kann. Wenn Daten vom Verhalten entkoppelt sind, müssen Sie das Verhalten nur durch Ihre Datendefinition erzwingen.quelle
In hohem Maße macht es die Unveränderlichkeit unnötig, Ihre Funktionen als OOP-Befürworter eng mit Ihren Daten zu koppeln. Sie können so viele Kopien erstellen, wie Sie möchten, und sogar abgeleitete Datenstrukturen in Code erstellen, der weit vom ursprünglichen Code entfernt ist, ohne zu befürchten, dass sich die ursprüngliche Datenstruktur unerwartet von Ihnen abhebt.
Allerdings ist eine bessere Möglichkeit , diesen Vergleich zu machen , ist wahrscheinlich zu sehen , welche Funktionen Sie auf das Modell sind die Zuteilung Schicht gegenüber der Dienste - Schicht . Auch wenn es nicht so aussieht wie in OOP, ist es ein häufiger Fehler in FP, zu versuchen, mehrere Abstraktionsebenen in eine Funktion zu packen.
Soweit ich weiß, nennt es niemand ein anämisches Modell, da dies ein OOP-Begriff ist, aber der Effekt ist der gleiche. Sie können und sollten generische Funktionen nach Bedarf wiederverwenden. Für komplexere oder anwendungsspezifischere Vorgänge sollten Sie jedoch auch eine Vielzahl von Funktionen bereitstellen, die nur für die Arbeit mit Ihrem Modell erforderlich sind. Das Erstellen geeigneter Abstraktionsebenen ist in jedem Paradigma ein gutes Design.
quelle
Wenn Sie DDD in OOP verwenden, liegt einer der Hauptgründe dafür, dass Geschäftslogik in die Domänenobjekte selbst eingefügt wird, darin, dass die Geschäftslogik normalerweise durch Ändern des Status des Objekts angewendet wird. Dies hängt mit der Kapselung zusammen:
Employee.RaiseSalary
mutiert wahrscheinlich dassalary
Feld derEmployee
Instanz, das nicht öffentlich einstellbar sein sollte.In FP wird Mutation vermieden. Sie implementieren dieses Verhalten, indem Sie eine
RaiseSalary
Funktion erstellen , die eine vorhandeneEmployee
Instanz verwendet und eine neueEmployee
Instanz mit dem neuen Gehalt zurückgibt . Es handelt sich also nicht um eine Mutation: Es wird nur vom ursprünglichen Objekt gelesen und das neue Objekt erstellt. Aus diesem Grund muss eine solcheRaiseSalary
Funktion nicht als Methode für dieEmployee
Klasse definiert werden, sondern kann überall verwendet werden.In diesem Fall ist es selbstverständlich, die Daten vom Verhalten zu trennen: Eine Struktur stellt die
Employee
as-Daten dar (vollständig anämisch), während ein (oder mehrere) Module Funktionen enthalten, die diese Daten verarbeiten (wobei die Unveränderlichkeit erhalten bleibt).Beachten Sie, dass Sie beim Koppeln von Daten und Verhalten wie bei DDD im Allgemeinen gegen das Einzelverantwortungsprinzip (Single Responsibility Principle, SRP) verstoßen:
Employee
Möglicherweise müssen Sie Änderungen vornehmen, wenn sich die Regeln für Gehaltsänderungen ändern. Es kann jedoch auch erforderlich sein, Änderungen vorzunehmen, wenn sich die Regeln für die Berechnung des EOY-Bonus ändern. Bei dem entkoppelten Ansatz ist dies nicht der Fall, da Sie mehrere Module mit jeweils einer Verantwortung haben können.Daher bietet der FP-Ansatz wie üblich eine größere Modularität / Zusammensetzbarkeit.
quelle
Ich denke, das Wesentliche dabei ist, dass ein anämisches Modell mit der gesamten Domänenlogik in Diensten, die auf dem Modell basieren, im Grunde eine prozedurale Programmierung ist - im Gegensatz zu einer "echten" OO-Programmierung, bei der Sie Objekte haben, die "intelligent" sind und nicht nur Daten enthalten sondern auch die Logik, die am engsten mit den Daten verbunden ist.
Und der gleiche Gegensatz besteht bei der funktionalen Programmierung: "echtes" FP bedeutet, Funktionen als erstklassige Entitäten zu verwenden, die als Parameter herumgereicht sowie im laufenden Betrieb konstruiert und als Rückgabewert zurückgegeben werden. Wenn Sie jedoch nicht die gesamte Leistung nutzen und nur Funktionen verwenden, die auf Datenstrukturen angewendet werden, die zwischen ihnen ausgetauscht werden, befinden Sie sich am selben Ort: Sie führen im Grunde genommen die prozedurale Programmierung durch.
quelle
Ich denke schon, aber hauptsächlich als taktischer Ansatz für den Übergang zwischen unveränderlichen Wertobjekten oder als Möglichkeit, Methoden für Entitäten auszulösen . (Wo der größte Teil der Logik noch in der Entität lebt.)
Wenn Sie "analog zum traditionellen OOP" meinen, hilft es, die üblichen Implementierungsdetails zu ignorieren und zu den Grundlagen zurückzukehren: Welche Sprache verwenden Ihre Domain-Experten? Welche Absicht erfassen Sie von Ihren Benutzern?
Angenommen , sie sprechen über gemeinsam Prozesse und Funktionen verketten, dann scheint es , Funktionen (oder zumindest „do-er“ Objekte) im Grunde sind Ihre Domain-Objekte!
In diesem Szenario würde ein "anämisches Modell" wahrscheinlich auftreten, wenn Ihre "Funktionen" nicht tatsächlich ausführbar sind, sondern nur Konstellationen von Metadaten, die von einem Dienst interpretiert werden, der die eigentliche Arbeit leistet.
quelle