DDD - Aggregierte Wurzel mit einer großen Anzahl von Kindern

10

Ich werde diese Frage vorwegnehmen, indem ich sage, dass ich für DDD relativ neu bin, sodass ich hier möglicherweise einige grundlegende Fehler mache!

Ich arbeite an einem Projekt, das die Konzepte von Konten und Transaktionen (im finanziellen Sinne) beinhaltet. Für ein Konto können viele Transaktionen eingegeben werden.

Es scheint mir, dass Konto und Transaktion beide Entitäten sind und dass Konto ein aggregierter Stamm ist, der Transaktionen enthält, da eine Transaktion ohne das Konto nicht existieren kann.

Wenn ich dies jedoch im Code anwende, stoße ich sofort auf ein Problem. In vielen Situationen ist es für mich nicht besonders nützlich, jederzeit eine Liste aller Transaktionen in einem Konto zu haben. Ich bin daran interessiert, beispielsweise den Kontostand berechnen und Invarianten wie ein Kreditlimit durchsetzen zu können, möchte aber auch problemlos mit einer Teilmenge von Transaktionen arbeiten können (z. B. Anzeigen von Transaktionen, die in einen Datumsbereich fallen).

Im letzteren Fall konnte ich mit einem TransactionRepositoryeffizient auf genau die Objekte zugreifen, die ich benötige, ohne die gesamte (möglicherweise sehr große) Liste zu laden. Dies würde es jedoch anderen Dingen als dem Konto ermöglichen, mit Transaktionen zu arbeiten, was bedeutet, dass ich das Konzept des Kontos als aggregierte Wurzel gebrochen habe.

Wie gehen Menschen mit solchen Situationen um? Akzeptieren Sie nur die Auswirkungen auf das Gedächtnis und die Leistung beim Laden einer potenziell großen Anzahl von untergeordneten Elementen für eine aggregierte Wurzel?

krixon
quelle

Antworten:

9

Ich würde empfehlen, vorsichtig mit der Regel "Kann nicht ohne existieren" umzugehen. Dies spricht für das Konzept der Komposition im UML / OO-Design und war möglicherweise einer der vorgeschriebenen Ansätze für das Design von Aggregaten im ursprünglichen DDD-Blue Book (nicht sicher), wurde jedoch seitdem weitgehend überarbeitet. Es könnte eine bessere Idee, um Ihre Aggregate aus einer Transaktions zu sehen Konsistenz Grenze Perspektive.

Die Idee ist, Ihre Aggregate weder zu groß zu machen, wenn Sie Leistungsprobleme haben, auf die Sie hinweisen, noch zu klein, da einige Invarianten unweigerlich mehrere Aggregate umfassen würden - was zu Problemen beim Sperren von Aggregaten und bei der Parallelität führen würde.

Die richtige Gesamtgröße passt idealerweise zu den Konturen dessen, was Sie in einem bestimmten Geschäftsvorgang ändern, nicht mehr und nicht weniger. Wenn in Ihrem Beispiel nicht viele Domäneninvarianten vorhanden sind, die sich über mehrere Finanztransaktionen erstrecken, ist es Transactionmöglicherweise die beste Lösung, einen eigenen aggregierten Stamm zu erstellen.

guillaume31
quelle
Vielen Dank, ich werde mich über Konsistenzgrenzen informieren. Ich denke, Ihr Vorschlag, Transaction zu einer eigenen Gesamtwurzel zu machen, könnte gut sein. Wie Sie sagen, habe ich nicht viele Invarianten, die sich über mehrere Transaktionen erstrecken.
Nixon
7

tl; dr - brechen Sie die Regeln, wenn Sie müssen. DDD kann nicht alle Probleme lösen. Tatsächlich sind die darin enthaltenen Objektideen gute Ratschläge und ein guter Anfang, aber für einige geschäftliche Probleme wirklich schlechte Entscheidungen. Betrachten Sie es als einen Hinweis, wie man Dinge macht.


Für das Problem, alle untergeordneten Elemente (Transaktion) mit dem übergeordneten Element (Konto) zu laden - Anscheinend sind Sie auf das n + 1-Problem (etwas zu googeln) gestoßen, das viele ORMs gelöst haben.

Sie können es lösen, indem Sie die untergeordneten Elemente faul laden (Transaktion) - nur bei Bedarf.

Aber es hört sich so an, als ob Sie bereits wissen, dass Sie ein TransactionRepository verwenden können, um das Problem zu lösen.

Um diese Daten zu "verbergen", damit nur das Konto sie verwenden kann, müssen Sie sie nicht einmal dort speichern, wo andere sie nicht deserialisieren können, wie in einer öffentlichen relationalen Tabelle. Sie können es mit dem 'Dokument' des Kontos in einer Dokument-DB speichern lassen. So oder so, wenn jemand sich genug Mühe gab, konnte er die Daten immer noch sehen. Und damit arbeiten. Und wenn Sie nicht suchen, werden sie!

Sie können also Berechtigungen einrichten, müssen dann aber 'account' als separaten Prozess ausführen.

Was Sie hier wirklich erkennen, ist, dass DDD und die reine Verwendung des Objektmodells Sie manchmal in eine Ecke führen. Um ehrlich zu sein, müssen Sie natürlich nicht die 'Komposition' / Aggregatwurzel verwenden, um von den Designprinzipien von DDD zu profitieren. Es ist nur eine Sache, die Sie verwenden können, wenn Sie eine Situation haben, die in ihre Einschränkungen passt.

Jemand könnte sagen "nicht frühzeitig optimieren". Hier in diesem Fall kennen Sie jedoch die Antwort: Es wird genügend Transaktionen geben, um eine Methode zu blockieren, die sie alle für immer im Konto behält.

Die wirkliche Antwort ist, SOA aufzustehen. An meinem Arbeitsplatz haben wir uns die Udi Dahan-Videos "Distributed Computing" angesehen und nServiceBus gekauft (nur unsere Wahl). Erstellen Sie einen Dienst für Konten - mit einem eigenen Prozess, Nachrichtenwarteschlangen, Zugriff auf eine Beziehungsdatenbank, die nur angezeigt wird, und ... Viola, Sie können SQL-Anweisungen im Programm fest codieren und sogar ein paar Cobol-Transaktionsskripte einfügen (Scherz) natürlich), aber ernsthaft mehr Trennung von Bedenken, als der klügste OO / Java-Snob jemals träumen könnte.

Ich würde empfehlen, es trotzdem gut zu modellieren. Sie können hier einfach die Vorteile von Aggregat Root ohne Probleme nutzen, indem Sie den Service als mini-begrenzten Countext behandeln.

Dies hat natürlich einen Nachteil. Sie können nicht nur RPC (Webservice, SOAP oder REST) ​​in und aus Diensten und zwischen ihnen einbinden, oder Sie erhalten aufgrund der zeitlichen Kopplung ein SOA-Antimuster namens "The Knot". Sie müssen die Umkehrung des Kommunikationsmusters verwenden, auch bekannt als "Pub-Sub", das genau wie Ereignishandler und Ereignisauslöser ist, aber (1) zwischen Prozessen (die Sie auf separate Computer stellen können, wenn diese auf einem überlastet werden).

Das eigentliche Problem ist, dass Sie nicht möchten, dass ein Dienst, der Daten von einem anderen Dienst erhalten muss, blockiert oder wartet. Sie müssen die Nachricht auslösen und vergessen und sie von einem Handler an einer anderen Stelle in Ihrem Programm abholen lassen, um die Verarbeitung abzuschließen. Dies bedeutet, dass Sie Ihre Logik anders machen müssen. nServicebus automatisiert das "Saga" -Muster, um einige dieser Probleme zu lösen. Am Ende müssen Sie jedoch einen anderen Codierungsstil entwickeln. Sie können immer noch alles tun, müssen es nur anders machen!

Das Buch "SOA Patterns" von Arnon Rotem-Gal-Oz beantwortet viele Fragen dazu. Einschließlich der Verwendung des "aktiven Dienstmusters", um Daten von externen Diensten bei Bedarf regelmäßig auf Ihre eigenen zu replizieren (viele RPCs wären erforderlich gewesen oder die Verknüpfung ist unzuverlässig / nicht im Publish / Subscribe-Ökosystem).

Nur um Vorschau, UIs Sie auf RPC in Dienstleistungen. Berichte werden aus einer Berichtsdatenbank generiert, die von den Datenbanken der Dienste gespeist wird. Einige Leute sagen, dass Berichte nicht benötigt werden und dass das Problem auf andere Weise gelöst werden sollte. Seien Sie skeptisch gegenüber diesem Gespräch.

Am Ende können jedoch nicht alle Dinge richtig in einen einzigen Dienst eingeteilt werden. Die Welt läuft nicht mit Ravioli-Code! Sie müssen also gegen Regeln verstoßen. Selbst wenn Sie es niemals müssten, werden neue Entwickler des Projekts dies tun, wenn Sie es verlassen. Aber keine Sorge, wenn Sie tun, was Sie können, machen die 85%, die den Regeln folgen, ein Programm, das weitaus wartbarer ist.

Wow, das war lang.

FastAl
quelle
Vielen Dank für die ausführliche Antwort, ich werde auf jeden Fall etwas über SOA nachlesen.
Nixon