Suchen Sie die DDD-Aggregatwurzel

10

Lass uns das Lieblingsspiel aller spielen und die Aggregrate Root finden. Verwenden wir die kanonische Problemdomäne Customer / Order / OrderLines / Product. Traditionell sind Kunde, Bestellung und Produkt die ARs, wobei OrderLines Einheiten unter der Bestellung sind. Die Logik dahinter ist, dass Sie Kunden, Bestellungen und Produkte identifizieren müssen, aber eine Bestelllinie würde ohne eine Bestellung nicht existieren. In unserer Problemdomäne haben wir eine Geschäftsregel, die besagt, dass ein Kunde jeweils nur eine nicht zugestellte Bestellung haben kann.

Verschiebt das die Bestellung unter den Kundenstamm? Ich denke schon. Dies macht den Kunden-AR jedoch ziemlich groß und kann später zu Parallelitätsproblemen führen.

Oder was wäre, wenn wir eine Geschäftsregel hätten, die besagt, dass ein Kunde ein bestimmtes Produkt nur einmal in seinem Leben bestellen kann? Dies ist ein weiterer Beweis dafür, dass der Kunde die Bestellung besitzen muss.

Aber wenn es um den Versand geht, machen sie alle ihre Aktionen auf der Bestellung, nicht auf dem Kunden. Es ist ziemlich dumm, den gesamten Kunden laden zu müssen, um eine einzelne Bestellung als geliefert zu markieren.

Das schlage ich vor:

class Customer
{
    public Guid Id {get;set;}
    public string Name { get; set; }
    public Address Address { get; set; }
    public IEnumerable<Order> Orders { get; set; }
    public void PlaceOrder(ThingsInTheOrder thingsInTheOrder)
    {
        // Make sure there aren't any pending orders already.
        // Make sure they aren't ordering a Widget if they've already ordered a Widget in the past.
        // Create an Order object and add it to the collection.  Raise a domain event to trigger emails and other stuff
    }
}

class Order
{
    public Guid Id { get; set; }
    public IEnumerable<OrderLine> OrderLines { get; set; }
    public ShippingData {get;set;}
    public void Ship(ShippedByPerson shippedByPerson, string trackingCode)
    {
         // Create a new ShippingData object and assign it from the data passed in.  
         // Publish a domain event
    }
}

Meine größte Sorge ist das Problem der Parallelität und die Tatsache, dass der Auftrag selbst Merkmale einer aggregierten Wurzel aufweist.

Darthg8r
quelle

Antworten:

12

Kurzfassung

Der Grund für DDD ist, dass Domänenobjekte Abstraktionen sind, die Ihre funktionalen Domänenanforderungen erfüllen sollten. Wenn Domänenobjekte diese Anforderungen nicht einfach erfüllen können, deutet dies darauf hin, dass Sie möglicherweise die falsche Abstraktion verwenden.

Das Benennen von Domänenobjekten mithilfe von Entitätsnomen kann dazu führen, dass diese Objekte eng miteinander gekoppelt werden und aufgeblähte "Gott" -Objekte werden, und sie können Probleme wie das in dieser Frage aufgeworfene wie "Wo ist der richtige Ort, um das zu platzieren?" Aufwerfen CreateOrder-Methode? ".

Um die Identifizierung des richtigen Aggregatstamms zu vereinfachen, sollten Sie einen anderen Ansatz in Betracht ziehen, bei dem Domänenobjekte auf den funktionalen Geschäftsanforderungen auf hoher Ebene basieren. Wählen Sie daher Substantive aus, die auf funktionale Anforderungen und / oder Verhaltensweisen verweisen, die Benutzer des Systems benötigen ausführen.


Lange Version

DDD ist ein Ansatz für OO-Design, der zu einem Diagramm der Domänenobjekte in der Geschäftsschicht Ihres Systems führen soll. Domänenobjekte sind für die Erfüllung Ihrer Geschäftsanforderungen auf hoher Ebene verantwortlich und sollten sich im Idealfall auf die Datenschicht verlassen können für Dinge wie die Leistung und Integrität des zugrunde liegenden persistenten Datenspeichers.

Eine andere Sichtweise könnten die Aufzählungspunkte in dieser Liste sein

  • Entitätsnomen schlagen normalerweise Datenattribute vor.
  • Domain-Nomen sollten Verhalten vorschlagen
  • DDD- und OO-Modellierung befassen sich mit Abstraktionen, die auf funktionalen Anforderungen und der Kerndomänen- / Geschäftslogik basieren.
  • Die Business Logic Layer ist dafür verantwortlich, die Domänenanforderungen auf hoher Ebene zu erfüllen

Eines der häufigsten Missverständnisse in Bezug auf DDD ist, dass Domänenobjekte auf einem physischen "Ding" der realen Welt basieren sollten (dh auf einem Substantiv, auf das Sie in der realen Welt verweisen können, das allen Arten von Daten / Eigenschaften zugeordnet ist), jedoch auf den Daten / Attribute dieser realen Dinge sind nicht unbedingt ein guter Ausgangspunkt, wenn versucht wird, funktionale Anforderungen festzulegen.

Natürlich sollte Business - Logik verwendet diese Daten, sondern Domain Objekte sich letztlich Abstraktionen , die funktionelle Domain Anforderungen und Verhaltensweisen darstellen sollten.

Zum Beispiel; Substantive wie Orderoder Customerimplizieren kein Verhalten und sind daher im Allgemeinen nicht hilfreiche Abstraktionen für die Darstellung von Geschäftslogik und Domänenobjekten.

Berücksichtigen Sie bei der Suche nach Abstraktionen, die für die Darstellung von Business Logic nützlich sein können, typische Anforderungen, die ein System möglicherweise erfüllen wird:

  • Als Verkäufer möchte ich eine Bestellung für einen neuen Kunden erstellen, damit ich eine Rechnung für die zu verkaufenden Produkte mit ihren Preisen und Mengen erstellen kann.
  • Als Kundendienstberater möchte ich eine ausstehende Bestellung stornieren, damit die Bestellung nicht von einem Lagerbetreiber ausgeführt wird.
  • Als Kundendienstberater möchte ich eine Bestellposition zurücksenden, damit das Produkt in den Lagerbestand aufgenommen werden kann und die Zahlung über die ursprüngliche Zahlungsmethode des Kunden zurückerstattet wird.
  • Als Lagerbetreiber möchte ich alle Produkte in einer ausstehenden Bestellung und die Versandinformationen anzeigen, damit ich die Produkte auswählen und über den Kurier versenden kann.
  • usw.

Modellierung von Domänenanforderungen mit einem DDD-Ansatz

Berücksichtigen Sie anhand der obigen Liste einige potenzielle Domänenobjekte für ein solches Auftragssystem:

SalesOrderCheckout
PendingOrdersStream
WarehouseOrderDespatcher
OrderRefundProcessor 

Als Domänenobjekte stellen diese Abstraktionen dar, die Eigentümer verschiedener Verhaltensdomänenanforderungen sind. in der Tat weisen ihre Substantive stark auf die spezifischen funktionalen Anforderungen hin, die sie erfüllen.

(Möglicherweise befindet sich dort auch eine zusätzliche Infrastruktur, z. B. eine EventMediatorBenachrichtigung für Beobachter, die wissen möchten, wann eine neue Bestellung erstellt wurde oder wann eine Bestellung versendet wurde usw.).

Zum Beispiel muss SalesOrderCheckoutwahrscheinlich Daten über Kunden, Versand und Produkte verarbeitet werden, hat jedoch nichts mit dem Verhalten für Versandaufträge, das Sortieren ausstehender Bestellungen oder das Ausstellen von Rückerstattungen zu tun.

Um SalesOrderCheckoutdie Domänenanforderungen zu erfüllen, müssen diese Geschäftsregeln durchgesetzt werden, z. B. verhindert werden, dass Kunden zu viele Artikel bestellen, möglicherweise eine Validierung durchführen und möglicherweise Benachrichtigungen für andere Teile des Systems auslösen. All diese Aufgaben können ausgeführt werden, ohne dass Sie sich darauf verlassen müssen der anderen Objekte.

DDD verwendet Entitätsnomen zur Darstellung von Domänenobjekten

Es gibt eine Reihe von möglichen Gefahren , wenn einfache Substantive wie die Behandlung Order, Customerund Productals Domain - Objekte; Zu diesen Problemen gehören diejenigen, auf die Sie in der Frage anspielen:

  • Wenn eine Methode ein Order, ein Customerund ein behandelt Product, zu welchem ​​Domänenobjekt gehört sie?
  • Wo ist die aggregierte Wurzel für diese 3 Objekte?

Wenn Sie Entitätsnomen auswählen, um Domänenobjekte darzustellen, kann eine Reihe von Dingen passieren:

  • Order, CustomerUnd Productsind in Gefahr , in „Gott“ Objekte des Wachsens
  • Das Risiko, mit einem einzigen ManagerGottobjekt zu enden , um alles zusammenzubinden.
  • Diese Objekte laufen Gefahr, eng miteinander verbunden zu werden. Es kann schwierig sein, die Domänenanforderungen zu erfüllen, ohne sie zu bestehen this(oder self).
  • Das Risiko, "undichte" Abstraktionen zu entwickeln - dh Domänenobjekte, von denen erwartet wird, dass sie Dutzende von get/ setMethoden offenlegen, die die Kapselung schwächen (oder, wenn Sie dies nicht tun, wird es wahrscheinlich später ein anderer Programmierer tun ..).
  • Es besteht die Gefahr, dass Domain-Objekte durch eine komplexe Mischung aus Geschäftsdaten (z. B. Eingabe von Benutzerdaten über eine Benutzeroberfläche) und vorübergehendem Status (z. B. eine 'Historie' von Benutzeraktionen, wenn die Bestellung geändert wurde) aufgebläht werden.

DDD, OO Design und einfache Modelle

Ein häufiges Missverständnis in Bezug auf DDD und OO Design ist, dass "einfache" Modelle irgendwie "schlecht" oder "anti-pattern" sind. Martin Fowler schrieb einen Artikel, der das anämische Domänenmodell beschreibt - aber wie er in dem Artikel klarstellt, sollte DDD selbst dem Ansatz einer sauberen Trennung zwischen Schichten nicht „widersprechen“

"Es ist auch hervorzuheben, dass das Einfügen von Verhalten in Domänenobjekte nicht dem soliden Ansatz widersprechen sollte, mithilfe von Layering die Domänenlogik von Dingen wie Persistenz und Präsentationsverantwortung zu trennen. Die Logik, die in einem Domänenobjekt enthalten sein sollte, ist Domänenlogik - Validierungen, Berechnungen , Geschäftsregeln - wie auch immer Sie es nennen möchten. "

Mit anderen Worten, die Verwendung einfacher Modelle zum Speichern von Geschäftsdaten, die zwischen anderen Ebenen übertragen werden (z. B. ein Auftragsmodell, das von einer Benutzeranwendung übergeben wird, wenn der Benutzer einen neuen Auftrag erstellen möchte), ist nicht dasselbe wie ein "anämisches Domänenmodell". "Einfache" Datenmodelle sind häufig der beste Weg, um Daten zu verfolgen und Daten zwischen Ebenen zu übertragen (z. B. ein REST-Webdienst, ein Persistenzspeicher, eine Anwendung oder eine Benutzeroberfläche usw.).

Die Geschäftslogik verarbeitet möglicherweise die Daten in diesen Modellen und verfolgt sie möglicherweise als Teil des Geschäftsstatus. Sie übernimmt jedoch nicht unbedingt das Eigentum an diesen Modellen.

Die aggregierte Wurzel

Betrachtet man erneut das Beispiel Domain Objekte - SalesOrderCheckout, PendingOrdersStream, WarehouseOrderDespatcher, OrderRefundProcessores gibt noch keine offensichtlichen Aggregate Wurzel; Dies spielt jedoch keine Rolle, da diese Domänenobjekte völlig unterschiedliche Verantwortlichkeiten haben, die sich nicht zu überschneiden scheinen.

Funktionell ist es nicht erforderlich, mit dem SalesOrderCheckoutzu sprechen, PendingOrdersStreamda der Job des ersteren abgeschlossen ist, wenn der Datenbank eine neue Reihenfolge hinzugefügt wurde. Auf der anderen Seite PendingOrdersStreamkann der neue Aufträge aus der Datenbank abrufen. Diese Objekte müssen nicht direkt miteinander interagieren (möglicherweise kann ein Ereignisvermittler Benachrichtigungen zwischen den beiden bereitstellen, aber ich würde erwarten, dass die Kopplung zwischen diesen Objekten sehr locker ist).

Möglicherweise ist der aggregierte Stamm ein IoC-Container, der eines oder mehrere dieser Domänenobjekte in einen UI-Controller einfügt und auch andere Infrastrukturen wie EventMediatorund bereitstellt Repository. Oder vielleicht ist es eine Art leichter Orchestrator-Service, der auf der Business-Ebene sitzt.

Das Aggregatstammverzeichnis muss nicht unbedingt ein Domänenobjekt sein. Um die Trennung von Bedenken zwischen Domänenobjekten aufrechtzuerhalten, ist es im Allgemeinen eine gute Sache, wenn der aggregierte Stamm ein separates Objekt ohne Geschäftslogik ist.

Ben Cottrell
quelle
3
Ich habe abgelehnt, weil Ihre Antwort Konzepte aus Entity Framework, einer Microsoft-spezifischen Technologie, mit Domain Driven Design, einem aus einem gleichnamigen Buch von Eric Evans, in Einklang bringt. Ihre Antwort enthält einige Aussagen, die in direktem Widerspruch zum DDD-Buch stehen. In dieser Frage wird Entity Framework nicht erwähnt, sie ist jedoch speziell mit DDD gekennzeichnet. In der Frage wird auch überhaupt keine Persistenz erwähnt, sodass ich nicht sehe, wo Datenbanktabellen relevant sind.
RibaldEddie
@RibaldEddie Vielen Dank, dass Sie sich die Zeit genommen haben, die Antwort und den Kommentar zu überprüfen. Ich stimme zu, dass die Erwähnung persistenter Daten nicht unbedingt in der Antwort enthalten sein muss. Deshalb habe ich sie umformuliert, um sie zu entfernen. Das Hauptaugenmerk der Antwort könnte wie folgt zusammengefasst werden: "Entitätsnomen sind aufgrund ihrer Tendenz, eng gekoppelte aufgeblähte Gottobjekte zu werden, oft keine sehr guten Domänenobjektklassennamen". Hoffentlich sind die Anforderungen und das Verhalten der WRT-Funktion und der Argumentation jetzt klarer ?
Ben Cottrell
Das DDD-Buch hat dieses Konzept IIRC nicht. Es hat Entitäten, die lediglich Klassen sind, die, wenn sie instanziiert werden, eine persistente und eindeutige Identität haben, so dass zwei getrennte Instanzen zwei eindeutige und persistente Dinge implizieren, was im Gegensatz zu Wertobjekten steht, die keine Identität haben und im Laufe der Zeit nicht persistieren . In diesem Buch sind sowohl Entities- als auch Value-Objekte Domänenobjekte.
RibaldEddie
10

Was sind die Kriterien für die Definition eines Aggregats?

Kehren wir zu den Grundlagen des großen blauen Buches zurück:

Aggregat: Ein Cluster zugeordneter Objekte, die zum Zweck von Datenänderungen als Einheit behandelt werden . Externe Verweise sind auf ein Mitglied des AGGREGATE beschränkt, das als Root bezeichnet wird. Innerhalb der Grenzen des AGGREGATE gelten eine Reihe von Konsistenzregeln.

Ziel ist es, die Invarianten zu erhalten. Es geht aber auch darum, die lokale Identität richtig zu verwalten, dh Objekte zu identifizieren, die allein keine Bedeutung haben.

Orderund Order linegehören definitiv zu einem solchen Cluster. Zum Beispiel:

  • Wenn Sie ein löschen Order, müssen alle Zeilen gelöscht werden.
  • Zum Löschen einer Zeile müssen möglicherweise die folgenden Zeilen neu nummeriert werden
  • Das Hinzufügen einer neuen Zeile würde erfordern, dass der Zeilennummer basierend auf allen anderen Zeilen derselben Reihenfolge bestimmt wird.
  • Das Ändern einiger Bestellinformationen, wie z. B. der Währung, kann sich auf die Bedeutung des Preises in den Werbebuchungen auswirken (oder eine Neuberechnung der Preise erforderlich machen).

Hier ist also das vollständige Aggregat erforderlich, um Konsistenzregeln und Invarianten sicherzustellen.

Wann aufhören?

Nun beschreiben Sie einige Geschäftsregeln und argumentieren, dass Sie den Kunden als Teil des Aggregats betrachten müssen, um dies sicherzustellen:

Wir haben eine Geschäftsregel, die besagt, dass ein Kunde jeweils nur eine nicht zugestellte Bestellung haben kann.

Natürlich, warum nicht. Lassen Sie uns die Auswirkungen sehen: Auf die Bestellung würde immer über den Kunden zugegriffen. Ist das echtes Leben? Wenn Mitarbeiter die Felder für die Lieferung der Bestellung ausfüllen, müssen sie dann den Kunden-Barcode und den Bestell-Barcode lesen, um auf die Bestellung zugreifen zu können? Tatsächlich ist die Identität eines Auftrags im Allgemeinen global und nicht lokal für einen Kunden, und diese relative Unabhängigkeit legt nahe, ihn außerhalb des Aggregats zu halten.

Darüber hinaus sehen diese Geschäftsregeln eher als Richtlinien aus: Es ist eine willkürliche Entscheidung des Unternehmens, den Prozess mit diesen Regeln auszuführen. Wenn die Regeln nicht eingehalten werden, ist der Chef möglicherweise unglücklich, aber die Daten sind nicht wirklich inkonsistent. Darüber hinaus könnte über Nacht "pro Kunde jeweils eine nicht zugestellte Bestellung" zu "zehn nicht zugestellten Bestellungen pro Kunde" oder sogar "unabhängig vom Kunden hundert nicht zugestellte Bestellungen pro Lager" werden, so dass das Aggregat möglicherweise nicht mehr gerechtfertigt ist.

Christophe
quelle
1

In unserer Problemdomäne haben wir eine Geschäftsregel, die besagt, dass ein Kunde jeweils nur eine nicht zugestellte Bestellung haben kann.

Bevor Sie zu tief in dieses Kaninchenloch vordringen, sollten Sie Greg Youngs Diskussion über die Konsistenz von Sätzen überprüfen , insbesondere:

Welche geschäftlichen Auswirkungen hat ein Ausfall?

Denn in vielen Fällen besteht die richtige Antwort nicht darin, zu versuchen, das Falsche zu verhindern, sondern Ausnahmemeldungen zu erstellen, wenn möglicherweise ein Problem vorliegt.

Unter der Annahme, dass mehrere nicht zugestellte Bestellungen eine erhebliche Belastung für Ihr Unternehmen darstellen ...

Ja, wenn Sie sicherstellen möchten, dass nur eine Bestellung nicht zugestellt wird, muss ein Aggregat vorhanden sein, das alle Bestellungen eines Kunden anzeigen kann.

Dieses Aggregat ist nicht unbedingt das Kundenaggregat .

Es kann sich um eine Bestellwarteschlange oder eine Bestellhistorie handeln, in der alle Bestellungen für einen bestimmten Kunden in dieselbe Warteschlange gestellt werden. Nach Ihren Angaben werden nicht alle Profildaten des Kunden benötigt, sodass dies nicht Teil dieses Aggregats sein sollte.

Aber wenn es um den Versand geht, machen sie alle ihre Aktionen auf der Bestellung, nicht auf dem Kunden.

Ja, wenn Sie tatsächlich mit Fulfillment- und Pull-Sheets arbeiten, ist die Verlaufsansicht nicht besonders relevant.

Die Verlaufsansicht benötigt zum Erzwingen Ihrer Invariante nur die Bestell-ID und den aktuellen Verarbeitungsstatus. Dies muss nicht unbedingt Teil desselben Aggregats wie die Reihenfolge sein. Denken Sie daran, dass es bei Aggregatgrenzen darum geht, Änderungen zu verwalten und keine Ansichten zu strukturieren.

Es kann also sein, dass Sie die Bestellung als Aggregat und den Bestellverlauf als separates Aggregat behandeln und die Aktivität zwischen beiden koordinieren.

VoiceOfUnreason
quelle
1

Sie haben ein Beispiel für eine Strohperson erstellt. Es ist zu simpel und ich bezweifle, dass es ein reales System widerspiegelt. Ich würde diese Entitäten und das damit verbundene Verhalten nicht so modellieren, wie Sie es aus diesem Grund angegeben haben.

Ihre Klassen müssen den Status einer Bestellung so modellieren, dass er sich in mehreren Aggregaten widerspiegelt. Wenn der Kunde beispielsweise das System in den Zustand versetzt, in dem die Bestellanforderung des Kunden verarbeitet werden muss, kann ich ein Objektaggregat für Domänenentitäten erstellen, das als CustomerOrderRequestoder PendingCustomerOrderoder nur CustomerOrderals oder in welcher Sprache auch immer das Unternehmen verwendet, und es kann einen Zeiger auf beide enthalten der Kunde und die OrderLines und haben dann eine Methode wie canCustomerCompleteOrder()die, die von der Service-Schicht aufgerufen wird.

Dieses Domänenobjekt würde die Geschäftslogik enthalten, um festzustellen, ob die Bestellung gültig war oder nicht.

Wenn die Bestellung gültig und verarbeitet wäre, hätte ich eine Möglichkeit, dieses Objekt auf ein anderes Objekt zu übertragen, das die verarbeitete Bestellung darstellt.

Ich denke, das Problem mit Ihrem Verständnis ist, dass Sie ein erfundenes, stark vereinfachtes Beispiel für Aggregate verwenden. A PendingOrderkann ein eigenes Aggregat sein, das von einem UndeliveredOrdergetrennt ist und wieder von einem DeliveredOrderoder einem CancelledOrderoder was auch immer getrennt ist.

RibaldEddie
quelle
Obwohl Ihr Versuch einer geschlechtsneutralen Sprache amüsant ist, möchte ich darauf hinweisen, dass Frauen niemals auf Feldern stehen, um Krähen abzuschrecken.
Robert Harvey
@ RobertHarvey, das ist eine seltsame Sache, auf die ich mich in meinem Beitrag konzentrieren muss. Vogelscheuchen und Bildnisse sind im Laufe der Geschichte regelmäßig in weiblicher Form aufgetreten.
RibaldEddie
Sie hätten die Unterscheidung in Ihrem Beitrag nicht getroffen, wenn Sie sie nicht für wichtig gehalten hätten. In sprachlicher Hinsicht ist der Begriff "Strohmann"; Vorbehalte gegen Sexismus werden mit ziemlicher Sicherheit durch den Faktor "Was zum Teufel redet er?" übertroffen, der durch die Erfindung Ihres eigenen Begriffs entsteht.
Robert Harvey
5
@RobertHarvey Wenn jemand weiß, was Strohmann bedeutet, kann er sicher herausfinden, was Strohmann bedeutet, wenn er diesen Begriff nicht gehört hat. Können wir uns auf den Inhalt meines Beitrags konzentrieren, bitte schreiben Sie Software?
RibaldEddie
1

Vaughn Vernon erwähnt dies in seinem Buch "Implementing Domain-Driven Design" am Anfang von Kapitel 7 (Services):

"Oft ist der beste Hinweis darauf, dass Sie einen Dienst im Domänenmodell erstellen sollten, wenn sich die Operation, die Sie ausführen müssen, als Methode für ein Aggregat oder ein Wertobjekt fehl am Platz anfühlt."

In diesem Fall könnte es also einen Domain-Service namens "CreateOrderService" geben, der eine Kundeninstanz und die Liste der Artikel für die Bestellung übernimmt.

class CreateOrderService
{
    public Order CreateOrder(Customer customer, ThingsInTheOrder thingsInTheOrder)
    {
        // Get all the orders for the customer
        // Check if any of the things to be ordered exist in previous orders   
        // If none have been previously ordered, create the order and return it
        // Otherwise return null 
    }
}
claudius
quelle
1
Können Sie bitte näher erläutern, wie der Domänendienst bei der Behebung von Problemen mit der Parallelität in der Frage helfen kann?
ivenxu