Berechnete Werte und einfache Lesevorgänge - ein quälender Schmerz für meine domänengesteuerten Designs!

9

Das Problem, mit dem ich ständig konfrontiert bin, ist der Umgang mit berechneten Werten, die von der Domänenlogik gesteuert werden, während ich weiterhin effizient mit dem Datenspeicher arbeite.

Beispiel:

Ich sende eine Liste von Produkten aus meinem Repository über einen Dienst zurück. Diese Liste wird durch Paginierungsinformationen aus dem vom Client gesendeten Anforderungs-DTO begrenzt. Darüber hinaus gibt das DTO einen Sortierparameter an (eine clientfreundliche Aufzählung).

In einem einfachen Szenario funktioniert alles hervorragend: Der Dienst sendet Paging- und Sortierausdrücke an das Repo, und das Repo gibt eine effiziente Abfrage an die Datenbank aus.

Das alles bricht jedoch zusammen, wenn ich nach Werten sortieren muss, die aus meinem Domänenmodell im Speicher generiert wurden. Beispielsweise verfügt die Produktklasse über eine IsExpired () -Methode, die einen auf Geschäftslogik basierenden Bool zurückgibt. Jetzt kann ich nicht mehr auf Repo-Ebene sortieren und blättern - alles wäre im Speicher erledigt (ineffizient), und mein Dienst müsste wissen, wann diese Parameter an das Repo ausgegeben werden müssen und wann Sortieren / Paging durchgeführt werden muss selbst.

Das einzige Muster, das mir sinnvoll erscheint, besteht darin, den Status der Entität in der Datenbank zu speichern (machen Sie IsExpired () zu einem schreibgeschützten Feld und aktualisieren Sie es vor dem Speichern über die Domänenlogik). Wenn ich diese Logik in ein separates Repository "read model / dto" und "reporting" aufteile, mache ich mein Modell anämischer, als ich es gerne hätte.

Übrigens, jedes Beispiel, das ich für solche Berechnungen gesehen habe, scheint sich wirklich auf die In-Memory-Verarbeitung zu stützen und die Tatsache zu beschönigen, dass es auf lange Sicht weitaus weniger effizient ist. Vielleicht optimiere ich vorzeitig, aber das passt einfach nicht zu mir.

Ich würde gerne hören, wie andere damit umgegangen sind, da ich sicher bin, dass es bei fast allen Projekten mit DDD üblich ist.

Drogon
quelle

Antworten:

3

Ich glaube nicht, dass zwei verschiedene Domain-Modelle desselben Datenmodells Ihre Domain anämisch machen. Ein anämisches Domänenmodell ist eines, bei dem die sich häufig ändernde Geschäftslogik in einer Service-Schicht (oder, schlimmer noch, in der UI-Schicht) außerhalb der Domäne verborgen ist.

Die Trennung von Befehls- und Abfragedomänenmodellen wird häufig unterstützt und hat ein schönes Akronym, das Sie in CQRS (Command Query Responsibility Segregation) googeln können.

Verwenden des Domänenmodellmusters, Udi Dahan

Während ich in der Vergangenheit "erfolgreich" war, ein einzelnes beständiges Objektmodell zu erstellen, das sowohl Befehle als auch Abfragen behandelte, war es oft sehr schwierig, es zu skalieren, da jeder Teil des Systems das Modell in eine andere Richtung zog.

Es stellt sich heraus, dass Entwickler häufig höhere Anforderungen stellen, als das Unternehmen tatsächlich benötigt. Die Entscheidung, die Domänenmodellentitäten zum Anzeigen von Informationen für den Benutzer zu verwenden, ist nur ein solches Beispiel.

[...]

Für diejenigen, die alt genug sind, um sich zu erinnern, haben uns die Best Practices bei der Verwendung von COM + dazu geführt, separate Komponenten für die schreibgeschützte und die schreibgeschützte Logik zu erstellen. Ein Jahrzehnt später haben wir neue Technologien wie das Entity Framework, aber dieselben Prinzipien gelten weiterhin.

CQRS mit Akka-Akteuren und funktionalen Domänenmodellen, Debasish Ghosh

Greg Young hat einige großartige Sessions zu DDD und CQRS geliefert. 2008 sagte er: "Ein einziges Modell kann nicht für Berichterstellung, Suche und Transaktionsverhalten geeignet sein." Wir haben mindestens zwei Modelle - eines, das Befehle verarbeitet und Änderungen an ein anderes Modell weiterleitet, das Benutzeranfragen und -berichte bereitstellt. Das Transaktionsverhalten der Anwendung wird über das Rich-Domain-Modell von Aggregaten und Repositorys ausgeführt, während die Abfragen direkt aus einem de-normalisierten Datenmodell bereitgestellt werden.

CQRS, Martin Fowler

Die von CQRS eingeführte Änderung besteht darin, dieses konzeptionelle Modell zur Aktualisierung und Anzeige in separate Modelle aufzuteilen, die nach dem Vokabular von CommandQuerySeparation als Befehl bzw. Abfrage bezeichnet werden. Das Grundprinzip ist, dass für viele Probleme, insbesondere in komplizierteren Bereichen, das gleiche konzeptionelle Modell für Befehle und Abfragen zu einem komplexeren Modell führt, das beides nicht gut macht.

Kurz gesagt, Ihre Idee, das Befehlsmodell den Ablauf behandeln und an die Datenbank übergeben zu lassen, ist absolut in Ordnung. Lesen Sie den ersten Artikel oben durch und Sie werden ähnliche, aber komplexere Szenarien sehen.

pdr
quelle
2

SPEZIFIKATION

Ich weiß, dass Sie bereits eine Antwort akzeptiert haben, aber Sie haben nach DDD gefragt, und die genaue Übereinstimmung dafür nennt Evans eine "Spezifikation":
direkter Google Books-Link
Wenn dieser Link nicht funktioniert, suchen Sie in diesen Ergebnissen nach dem Buch
Es ist Seite 226, wenn Sie das Buch haben.

Auf Seite 227 werden drei Verwendungszwecke für Spezifikationen aufgeführt: Validierung, Auswahl, Erstellen eines neuen Spezialobjekts. Ihre ist 'Auswahl' - IsExpired.

Eine andere Sache des "Spezifischen" -Konzepts ist, dass es aus Effizienzgründen möglicherweise eine Codeversion benötigt, um die speicherinternen Objekte zu bearbeiten, und eine andere Version des Codes, um das Repository effizient abzufragen, ohne es zuerst abrufen zu müssen alle Objekte in den Speicher.

In einer einfachen Welt würde dies bedeuten, eine SQL-Version in Ihr Repository und eine Objektversion in Ihr Modell aufzunehmen, was natürlich Nachteile hat. Die Logik befindet sich an zwei Stellen (schlecht, jemand wird vergessen, eine dieser Stellen zu aktualisieren), und in Ihrem Repository befindet sich eine Domänenlogik.

Die Antwort besteht also darin, beide Logiksätze in eine Spezifikation aufzunehmen. Natürlich die In-Memory-Version, aber auch eine Repository-Version. Wenn Sie beispielsweise n-hibernate verwenden, können Sie die integrierte Abfragesprache für die Repository-Version verwenden.

Andernfalls müssen Sie eine spezielle Repository-Methode für diese Spezifikation erstellen, die vom Spezifikationsobjekt verwendet wird. Aufrufe für Sammlungen von Objekten, die der Spezifikation entsprechen, würden die Spezifikation durchlaufen, nicht das Repository. Und zumindest schreit der Code für zukünftige Betreuer: "Ich bin an zwei Stellen, vergiss es nicht." Auf Seite 231-232 finden Sie ein wunderbares Beispiel für die Lösung eines sehr ähnlichen Problems.

Die Spezifikation ist ein "zulässiges" Auslaufen / Verrutschen der "Reinheit" von DDD. Es kann immer noch nicht Ihren Bedürfnissen für eine Vielzahl von Zwecken dienen. Beispielsweise kann der ORM fehlerhaftes SQL generieren. Möglicherweise ist zu viel zusätzliche Codierung vorhanden. Daher müssen Sie möglicherweise Repository-Methoden so aufrufen, dass es fast so ist, als würde SQL in die Spezifikation aufgenommen. Eine schlechte Sache natürlich. Aber vergessen Sie nicht, Ihr Programm muss mit einer angemessenen Geschwindigkeit arbeiten. Es muss keinen DDD-Reinheitspreis gewinnen. Eine Realität des Wechsels von Datenspeichern könnte also eine altmodische Operation während des gesamten Programms bedeuten. Auch eine schlechte Sache. Aber nicht so schlimm wie ein langsames (auch bekannt als SUCKing) Programm. Wenn es offensichtlich ist, dass verschiedene DBs sofort einsatzbereit sind, duplizieren Sie natürlich die Geschäftsregeln für jeden Datenspeicher für jede Spezifikation. Zumindest haben Sie den Finger in der Sache und können das Strategiemuster verwenden, wenn Sie Repositorys tauschen. Wenn Sie jedoch eine bestimmte Datenbank verwenden, denken Sie daranYAGNI.

In Bezug auf CQRS: Fowlers Zitat von pdr oben gilt hier immer noch: "Das gleiche konzeptionelle Modell für Befehle und Abfragen führt zu einem komplexeren Modell, das weder gut funktioniert" ... und Sie müssen möglicherweise CQRS oder ähnliches verwenden. Unter dem Gesichtspunkt der Entwicklung und Wartung ist es jedoch viel teurer. Wenn Sie ein Paketanbieter sind, der im Wettbewerb mit anderen steht, zahlt sich dies möglicherweise aus. Wenn Sie eine benutzerdefinierte LOB-App für einen Kunden schreiben, ist das Fotografieren für Perfektion eine schlechte Wahl. Sie müssen entscheiden, ob der Wert eines vollständig oder größtenteils doppelten Modells den zusätzlichen Aufwand wert ist. Spezifikationist ein guter Kompromiss, da Sie diese Trennung in nur einem kleinen Teil des Programms vornehmen können, das sie benötigt, und zwar mit der (Entwicklungs-) Geschwindigkeit und Einfachheit eines Modells. Viel Glück!

FastAl
quelle
Das macht durchaus Sinn. Ich denke, ich muss in die Kugel beißen und Evans 'Buch lesen :-) Ich sehe jetzt, dass ein flaches Verständnis dieser Konzepte Sie wirklich lähmen kann!
Drogon
0

Ich denke, ich würde fragen, welche Geschäftslogik bestimmt, ob isExpired wahr ist oder nicht. Kann diese Logik von einer Abfrage ausgeführt werden, wenn das Datenmodell steht? Wenn ja, können Sie Ihr Repository intelligent genug machen, um die "isExpired" -Logik zu verwenden, wenn Sie es auf bestimmte Weise nach Produkten fragen? Wenn nicht, müssen Sie möglicherweise Ihr Datenmodell erneut überprüfen.

DDD bedeutet nicht, dass Ihre Repositorys dumm sein müssen - es bedeutet nur, dass Ihre Domain wissen muss, wie sie mit Ihren Repositorys kommunizieren kann.

Matthew Flynn
quelle