it-swarm.com.de

Ist es noch gültig, im Rahmen der funktionalen Programmierung über ein anämisches Modell zu sprechen?

Die meisten taktischen Entwurfsmuster von DDD gehören zu einem objektorientierten Paradigma, und ein anämisches Modell beschreibt die Situation, in der die gesamte Geschäftslogik eher in Services als in Objekte integriert wird, wodurch sie zu einer Art DTO werden. Mit anderen Worten, anämisches Modell ist ein Synonym für prozeduralen Stil, der für komplexe Modelle nicht empfohlen wird.

Ich bin nicht sehr erfahren in der reinen funktionalen Programmierung, aber ich würde gerne wissen, wie DDD in das Paradigma FP] passt und ob der Begriff 'anämisches Modell' in diesem Fall noch existiert.

Update : Zuletzt veröffentlicht Buch und Video zu diesem Thema.

Und noch ein Video von Scott.

44
Pavel Voronin

Die Art und Weise, wie das Problem des "anämischen Modells" beschrieben wird, lässt sich nicht gut auf FP wie es ist) übertragen. Zunächst muss es angemessen verallgemeinert werden. Im Kern ist ein anämisches Modell ein Modell, das Wissen enthält Informationen zur ordnungsgemäßen Verwendung, die nicht vom Modell selbst gekapselt wird. Stattdessen wird dieses Wissen auf einen Stapel verwandter Dienste verteilt. Diese Dienste sollten nur Clients des Modells sein, aber fällig zu seiner Anämie werden sie verantwortlich dafür gehalten. Stellen Sie sich beispielsweise eine Account -Klasse vor, die nicht zum Aktivieren oder Deaktivieren von Konten oder sogar zum Nachschlagen von Informationen zu einem Konto verwendet werden kann, es sei denn, sie wird über eine AccountManager -Klasse verarbeitet. Das Konto sollte für grundlegende Vorgänge verantwortlich sein, nicht für eine externe Managerklasse.

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 beliebige Zeichenfolgen verwendet werden. Es macht keinen Sinn, sie zu verketten oder Teilzeichenfolgen auszuschneiden. Unicode sollte eigentlich keine Rolle spielen und sie sollten leicht in URLs und andere Kontexte mit strengen Zeichen- und Formatbeschränkungen einbettbar sein.

Die Lösung dieses Problems erfolgt normalerweise in wenigen Schritten. Ein einfacher erster Schnitt lautet: "Nun, ein UserID wird äquivalent zu einem String dargestellt, aber es sind verschiedene Typen und Sie können keinen verwenden, bei dem Sie das erwarten." andere." Haskell (und einige andere typisierte Funktionssprachen) bietet diese Funktion über newtype:

newtype UserID = UserID String

Dies definiert eine UserID -Funktion, die, wenn ein String gegeben wird, einen Wert konstruiert, der vom Typsystem behandelt wie a UserID ist, aber welcher ist zur Laufzeit immer noch nur ein String. Jetzt können Funktionen deklarieren, dass sie ein UserID anstelle eines Strings benötigen. Verwenden von UserIDs, wo Sie zuvor Zeichenfolgenschutz gegen Code verwendet haben, der zwei UserIDs miteinander verkettet. Das Typsystem garantiert das kann nicht passieren, keine Tests erforderlich.

Die Schwäche hier ist, dass Code immer noch ein beliebiges String wie "hello" Nehmen und daraus ein UserID konstruieren kann. Weitere Schritte umfassen das Erstellen einer "intelligenten Konstruktor" -Funktion, die bei Angabe einer Zeichenfolge einige Invarianten überprüft und nur dann ein UserID zurückgibt, wenn sie erfüllt sind. Dann wird der "dumme" UserID -Konstruktor privat gemacht. Wenn ein Client einen UserID möchte, verwenden sie muss den intelligenten Konstruktor, wodurch verhindert wird, dass fehlerhafte Benutzer-IDs eingehen Existenz.

Noch weitere Schritte definieren den Datentyp UserID so, dass es unmöglich Ist, einfach per Definition einen zu erstellen, der fehlerhaft oder "unpassend" ist. Definieren Sie beispielsweise ein UserID als eine Liste von Ziffern:

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

Um ein UserID zu erstellen, muss eine Liste von Ziffern bereitgestellt werden. Angesichts dieser Definition ist es trivial zu zeigen, dass es unmöglich ist, dass ein UserID existiert, das nicht in einer URL dargestellt werden kann. Das Definieren solcher Datenmodelle in Haskell wird häufig durch erweiterte Systemfunktionen wie Data Kinds und Generalized Algebraic Data Types (GADTs) unterstützt, mit denen das Typsystem definiert und bewiesen werden kann mehr Invarianten über Ihren Code. Wenn Daten vom Verhalten entkoppelt sind, ist Ihre Datendefinition das einzige Mittel, mit dem Sie das Verhalten erzwingen müssen.

27
Jack

Unveränderlichkeit macht es zu einem großen Teil unnötig, Ihre Funktionen eng mit Ihren Daten zu koppeln, da OOP befürwortet. Sie können so viele Kopien erstellen, wie Sie möchten, und sogar abgeleitete Datenstrukturen in weit entferntem Code erstellen aus dem ursprünglichen Code, ohne Angst zu haben, dass sich die ursprüngliche Datenstruktur unerwartet unter Ihnen ändert.

Ein besserer Weg, um diesen Vergleich durchzuführen, besteht wahrscheinlich darin, zu prüfen, welche Funktionen Sie dem Modell Ebene gegenüber den Diensten Ebene zuweisen. 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 Begriff ist OOP Begriff, aber der Effekt ist der gleiche. Sie können und sollten generische Funktionen wo immer möglich wiederverwenden, jedoch für komplexere Bei anwendungsspezifischen Vorgängen sollten Sie auch eine Vielzahl von Funktionen bereitstellen, die nur für die Arbeit mit Ihrem Modell geeignet sind. Das Erstellen geeigneter Abstraktionsschichten ist in jedem Paradigma ein gutes Design.

10
Karl Bielefeldt

Bei Verwendung von DDD in OOP besteht einer der Hauptgründe für das Einfügen von Geschäftslogik in die Domänenobjekte selbst darin, dass Geschäftslogik normalerweise durch Mutieren des Status des Objekts angewendet wird. Dies hängt mit der Kapselung zusammen: Employee.RaiseSalary mutiert wahrscheinlich das Feld salary der Instanz Employee, das nicht öffentlich einstellbar sein sollte.

In FP wird eine Mutation vermieden, sodass Sie dieses Verhalten implementieren, indem Sie eine RaiseSalary -Funktion erstellen, die eine vorhandene Employee -Instanz verwendet und eine neEmployee Instanz mit dem neuen Gehalt. Es handelt sich also nicht um eine Mutation: Nur vom ursprünglichen Objekt lesen und das neue Objekt erstellen. Aus diesem Grund muss eine solche RaiseSalary -Funktion nicht als Methode für die Employee -Klasse definiert werden, sondern kann überall leben.

In diesem Fall ist es selbstverständlich, die Daten vom Verhalten zu trennen: Eine Struktur repräsentiert das Employee als Daten (vollständig anämisch), während ein (oder mehrere) Module Funktionen enthalten, die mit diesen Daten arbeiten (Unveränderlichkeit bewahren). .

Beachten Sie, dass Sie beim Koppeln von Daten und Verhalten wie in DDD im Allgemeinen gegen das Single Responsibility Principle (SRP) verstoßen: Employee muss möglicherweise geändert werden, wenn sich die Regeln für Gehaltsänderungen ändern. Es muss sich jedoch möglicherweise auch ändern, wenn sich die Regeln für die Berechnung des EOY-Bonus ändern. Beim entkoppelten Ansatz ist dies nicht der Fall, da Sie mehrere Module mit jeweils einer Verantwortung haben können.

Wie üblich bietet der Ansatz FP) eine größere Modularität/Zusammensetzbarkeit.

8
la-yumba

Ich denke, das Wesentliche ist, dass ein anämisches Modell mit der gesamten Domänenlogik in Diensten, die mit dem Modell arbeiten, im Grunde genommen prozedurale Programmierung ist - im Gegensatz zu "real" OO Programmierung, bei der Sie Objekte haben, die sind "intelligent" und enthalten nicht nur Daten, sondern auch die Logik, die am engsten mit den Daten verbunden ist.

Der gleiche Kontrast besteht bei der funktionalen Programmierung: "real" FP bedeutet, Funktionen als erstklassige Entitäten zu verwenden, die als Parameter weitergegeben, im laufenden Betrieb erstellt und als Rückgabewert zurückgegeben werden Wenn Sie jedoch nicht die gesamte Leistung nutzen und nur Funktionen haben, die mit Datenstrukturen arbeiten, die zwischen ihnen ausgetauscht werden, landen Sie an derselben Stelle: Sie programmieren im Grunde genommen prozedural.

0