Gilt es immer noch, im Kontext der funktionalen Programmierung von einem anämischen Modell zu sprechen?

40

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.

Update : Zuletzt veröffentlichtes Buch und Video zum Thema.

Pavel Voronin
quelle
1
Wenn Sie sagen, was ich denke, dass Sie hier sagen, sind DTOs anämische, aber erstklassige Objekte in DDD, und es gibt eine natürliche Trennung zwischen den DTOs und den Diensten, die sie verarbeiten. Ich stimme im Prinzip zu. Dies gilt anscheinend auch für diesen Blogeintrag .
Robert Harvey
1
"Gibt es in diesem Fall noch den Begriff" anämisches Modell "?" Kurz gesagt, der Begriff "anämisches Modell" wurde im Kontext von OO geprägt. Es macht überhaupt keinen Sinn, von einem anämischen Modell im Kontext von FP zu sprechen. Es mag Äquivalente im Sinne einer Beschreibung des idiomatischen FP geben, aber es hat nichts mit anämischen Modellen zu tun.
Plalx
5
Eric Evans wurde einmal gefragt, was er zu Leuten sagt, die ihn beschuldigen, dass das, was er in seinem Buch beschreibt, nur gutes objektorientiertes Design ist, und er antwortete, dass es keine Anschuldigung ist, es ist die Wahrheit, DDD ist nur gutes OOD, er hat gerade geschrieben Notieren Sie sich einige Rezepte und Muster und geben Sie ihnen Namen, damit es einfacher ist, ihnen zu folgen und darüber zu sprechen. Kein Wunder also, dass DDD mit OOD verknüpft ist. Die umfassendere Frage wäre, was Schnittmengen und Unterschiede zwischen OOD und FPD sind, obwohl Sie zunächst definieren müssten, was Sie unter "funktionaler Programmierung" verstehen.
Jörg W Mittag
2
@ JörgWMittag: Du meinst anders als die übliche Definition? Es gibt viele anschauliche Plattformen, von denen Haskell die offensichtlichste ist.
Robert Harvey

Antworten:

24

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 AccountKlasse 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 eine AccountManagerKlasse 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 UserIDwird 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 über newtype:

newtype UserID = UserID String

Dies definiert eine UserIDFunktion, die bei Angabe eines StringKonstrukts einen Wert erzeugt, der vom Typsystem wie ein behandeltUserID wird, der aber nur Stringzur Laufzeit vorliegt. Jetzt können Funktionen deklarieren, dass sie einen UserIDanstelle eines Strings benötigen . Verwenden von UserIDs, bei denen Sie zuvor Zeichenfolgen verwendet haben, um zu verhindern, dass Code zwei UserIDs 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 StringLike nehmen "hello"und daraus ein konstruieren UserIDkann. Weitere Schritte umfassen das Erstellen einer "Smart-Konstruktor" -Funktion, die bei Angabe eines Strings einige Invarianten überprüft und nur dann ein zurückgibt, UserIDwenn sie zufrieden sind. Dann wird der "dumme" UserIDKonstruktor privat gemacht. Wenn ein Client dies wünscht UserID, muss er den intelligenten Konstruktor verwenden, wodurch verhindert wird, dass fehlerhafte Benutzer-IDs entstehen.

In weiteren Schritten wird der UserIDDatentyp so definiert , dass es nicht möglich ist , einen fehlerhaften oder "unpassenden" Datentyp zu erstellen . So definieren Sie beispielsweise eine UserIDals Liste von Ziffern:

data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]

Zum Erstellen einer UserIDListe müssen Ziffern angegeben werden. In Anbetracht dieser Definition ist es trivial zu zeigen, dass es unmöglich ist UserID, 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.

Jack
quelle
2
Und was ist mit Aggregaten und aggregierten Wurzeln, die Invarianten schützen? Können die letzten auch ausgedrückt werden und von den Entwicklern später leicht nachvollzogen werden? Für mich ist der wertvollste Teil von DDD die direkte Zuordnung des Geschäftsmodells zum Code. Und genau darum geht es bei Ihrer Antwort.
Pavel Voronin
2
Nette Rede, aber keine Antwort auf die OP-Frage.
SerG
10

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.

Karl Bielefeldt
quelle
2
"In hohem Maße macht es die Unveränderlichkeit unnötig, Ihre Funktionen eng mit Ihren Daten zu koppeln, wie dies OOP befürwortet."
Giorgio
2
Der Hauptvorteil der Kopplung des Verhaltens mit Daten im Kontext von DDD besteht in der Bereitstellung einer aussagekräftigen geschäftsrelevanten Schnittstelle. Es ist einfach immer zur Hand. Wir haben eine natürliche Art, Code selbst zu dokumentieren (zumindest ist es das, was ich gewohnt bin), und das ist der Schlüssel für eine erfolgreiche Kommunikation mit Geschäftsexperten. Wie wird es dann in FP erreicht? Wahrscheinlich hilft Piping, aber was noch? Erschwert die generische Natur von FP nicht die Rückentwicklung von Geschäftsanforderungen aus dem Code?
Pavel Voronin
7

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.RaiseSalarymutiert wahrscheinlich das salaryFeld der EmployeeInstanz, das nicht öffentlich einstellbar sein sollte.

In FP wird Mutation vermieden. Sie implementieren dieses Verhalten, indem Sie eine RaiseSalaryFunktion erstellen , die eine vorhandene EmployeeInstanz verwendet und eine neue Employee 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 solche RaiseSalaryFunktion nicht als Methode für die EmployeeKlasse definiert werden, sondern kann überall verwendet werden.

In diesem Fall ist es selbstverständlich, die Daten vom Verhalten zu trennen: Eine Struktur stellt die Employeeas-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: EmployeeMö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.

la-yumba
quelle
-1

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.

Michael Borgwardt
quelle
5
Ja, das sagt das OP im Grunde in seiner Frage. Sie scheinen beide den Punkt verpasst zu haben, dass Sie immer noch funktionale Zusammensetzung
Robert Harvey
-3

Ich würde gerne wissen, wie DDD in das FP-Paradigma passt

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.)

und ob der Begriff "anämisches Modell" in diesem Fall noch existiert.

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.

Darien
quelle
1
Ein anämisches Modell tritt auf, wenn Sie abstrakte Datentypen wie Tupel, Datensätze oder Listen zur Verarbeitung an verschiedene Funktionen übergeben. Sie brauchen nicht annähernd so exotisch wie eine "Funktion, die nicht ausgeführt wird" (was auch immer das ist).
Robert Harvey
Daher die Anführungszeichen um "Funktionen", um hervorzuheben, wie unangemessen das Etikett wird, wenn sie anämisch sind.
Darien
Wenn Sie ironisch sind, ist es ein bisschen subtil.
Robert Harvey