Ankündigung

Einklappen
Keine Ankündigung bisher.

Domain-Driven-Design: Datenkonsistenz zwischen Aggregaten sicherstellen

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Domain-Driven-Design: Datenkonsistenz zwischen Aggregaten sicherstellen

    Moin,

    ich beschäftige mich momentan mit der Neukonzeptionierung der Software eines Buchgroßhandels. Da die alte Software praktisch kein nennenswertes Domain-Model hat (simple Top-Bottom-Scripte und Funktionen machen mit der Datenbank was sie wollen) besitzt, dachte ich mir dass es an der Zeit ist das ganze "ordentlich" anzugehen.

    Was Domain-Driven-Design ist war mir schon vorher bekannt, allerdings kam ich bisher noch nicht in die Verlegenheit das auch anwenden zu müssen, weswegen sich mir einige Probleme stellen welche ich mit reiner Recherche selber nicht lösen könnte.

    Meine Recherchen zu dem Thema (Threadtitel) haben bisher ergeben dass, wenn man in dieses Problem läuft, ein Designfehler im Model vorliegt. Vielleicht kann mich hier ja jemand in die richtige Richtung schubsen.

    Anforderungen:
    Um den Rahmen des Threads nicht zu sprengen reduziere ich die Anforderungen auf das Nötigste um mein Problem zu veranschaulichen.

    - Es sollen Bücher verkauft werden können. Die Exemplare verteilen sich auf mehrere Läger und Lagerplätze. Die Exemplare eines jeden Buches können sich beliebig auf unterschiedlichen Lagerplätzen befinden.
    - Wenn ein Buch bestellt wird soll geprüft werden ob ausreichend Exemplare über alle Läger und Lagerplätze vorhanden sind.
    - Ist das nicht der Fall soll die mögliche Lieferanzahl der Auftragsposition um die fehlende Anzahl von Exemplaren verringert werden.
    - Sind genug Exemplare vorhanden sollen diese Exemplare im Lager als verkauft markiert werden.
    - Außerdem muss nachvollzogen werden können welche Auftragsposition ihre Exemplare von welchen Lagerplätzen erhalten soll.

    Bisheriger Lösungsansatz:
    Auch wenn es sich aus den Anforderungen so herauslesen ließe sind Buchexemplare keine eigenen Entitäten. Auf einer Palette von Büchern, welche auf einen Lagerplatz geschoben wird, sind alle Bücher mit derselben ISBN als identisch anzusehen.

    Läger und Lagerplätze müssen unabhängig davon, ob sich nun Buchexemplare darauf befinden oder nicht existieren können.

    Das Lager muss mMn. als Aggregate-Root implementiert werden. Über das Aggregat werden dann die Lagerplätze als lokale Entities verwaltet deren ID der Name des Lagerplatzes als Value-Object ist.

    Der Lagerplatz könnte dann wiederum die Lagerbestände als Value-Objects verwalten.

    Auf der anderen Seite haben wir den Auftrag als Aggregate-Root und seine Auftragspositionen als Value-Objects.

    Wird nun auch Buch bestellt übergeben wir dieses mit einer Bestellmenge an das Auftrags-Aggregat welches daraus eine neue Auftragsposition formen kann.

    Was mache ich aber nun mit den Bedingungen für den Lagerbestand?

    Ich kann wohl nicht einfach alle Läger an die Bestell-Methode des Auftrags übergeben und dieser dann das Finden der Bestände überlassen.

    Lösung 1: Ich modelliere den Bestellprozess über einen Service. Theoretisch könnte dann aber ein anderer Service die Regeln des Lagerbestandes ignorieren.
    Lösung 2: Ich implementiere ein Domain-Event für das Erzeugen einer neuen Bestellposition auf welches das Lager reagieren kann. Dann habe ich aber "nur" eine "indirekte" Kopplung zwischen Lager und Auftrag. Was passiert wenn ein Event fehlschlägt?
    Problem: So oder so zieht der Prozess Änderungen in zwei Aggregaten mit sich. Da eine Transaktion aber immer nur ein Aggregat umfassen darf könnte der Persistierungsvorgang eines Aggregates fehlschlagen was zu einer Dateninkonsistenz führt welche über das Domain-Model ja gerade verhindert werden soll.

    Lösung 3: Gesetzt dem Fall ich überlasse dem Auftragsaggregat das Finden der Bestände könnte ich ein Value-Object erzeugen welches die Referenz auf das Lager, das Value-Object Lagerplatz und die Liefermenge enthält. Dadurch modelliere ich aber nur den Soll-Zustand, d.h. es kann Doppelverkäufe geben da das Lager nichts über die geplanten Liefermengen weiß und der nächste Bestellprozess die bereits als verkauft markierten Exemplare nicht berücksichtigen kann.
    Lösung 4: Ich modelliere den Lagerbestand in das Auftragsaggregat hinein. Das ist aber per se schon Quatsch da auf lange Sicht gesehen natürlich nicht nur das Auftragswesen mit den Lagerbeständen interagieren muss.

    Ich wäre sehr dankbar wenn mich jemand in die richtige Richtung schubsen könnte.
    "Alles im Universum funktioniert, wenn du nur weißt wie du es anwenden musst".

  • #2
    Ich hab mich noch nie explizit mit DDD beschäftigt. Meine erste Frage wäre:
    Was sollen Deine Domains genau sein? Ich sehe da z.B. in der Wortwahl Fragezeichen bei Auftrag und Bestellung.

    Naheliegend wäre natürlich das Lager (stock) als eine Domain, das in mehreren Instanzen auftauchen kann. Ein Lager muss nur ein und auslagern könnten und Bestandsabfragen bedienen können, vielleicht auch Picking Anforderungen. Ein Lager muss wissen, wo es ist, wieviel Platz es hat und wieviel davon frei ist.
    Du hast eine Bestell Domain (order) usw.
    Dann hast Du Services, die das Shipment regeln, die ggF. das Auslagern regeln bzw. optimieren.

    Dass es um Bücher geht, ist m.E. relativ egal. Es braucht ein product Domain, die Buch spezifische Attribute verwalten kann.

    Der Auftrag wäre m.E. genauso sparsam zu implementieren wie das Lager. Er würde nicht selbst im Lager rumwühlen. Das macht ein collect service oder sowas , vielleicht auch verschiedene services, je nach Entwicklungsstand. An der Stelle fängt ja so ein System erst an, Sinn zu ergeben. Die Vorstellung, dass es verschiedene Service Implementierungen zwischen Auftrag und Lager geben soll / kann, die alle dazu führen, dass der Kunde seine Bücher bekommt, alle aber ganz anders funktionieren und sich zwangsläufig mit den definierten Interfaces von Auftrag und Lager begnügen müssen, ist doch sehr plastisch und vielleicht sogar hilfreich beim Design.

    Achso, welche Vorteile siehst Du in dem DDD Verfahren für Deinen Anwendungsfall hier?

    Kommentar


    • #3
      Danke für deine Antwort.

      Zitat von Perry Staltic Beitrag anzeigen
      Ich hab mich noch nie explizit mit DDD beschäftigt. Meine erste Frage wäre:
      Was sollen Deine Domains genau sein? Ich sehe da z.B. in der Wortwahl Fragezeichen bei Auftrag und Bestellung.
      Ich habe versucht mich an der Wortwahl des Kunden zu orientieren. Für diesen ist Auftrag = Bestellung und Auftrag ist der Terminus welcher im Kundenunternehmen verwendet wird.

      Eine Domain, welcher auch der Terminologie des Kunden entspricht, wäre Verkaufswesen, da dort u.A. auch der Kundenstamm, Reservierungen u.Ä. reinfallen würden.

      Zitat von Perry Staltic Beitrag anzeigen
      Naheliegend wäre natürlich das Lager (stock) als eine Domain, das in mehreren Instanzen auftauchen kann. Ein Lager muss nur ein und auslagern könnten und Bestandsabfragen bedienen können, vielleicht auch Picking Anforderungen. Ein Lager muss wissen, wo es ist, wieviel Platz es hat und wieviel davon frei ist.
      Du hast eine Bestell Domain (order) usw.
      Dann hast Du Services, die das Shipment regeln, die ggF. das Auslagern regeln bzw. optimieren.

      Dass es um Bücher geht, ist m.E. relativ egal. Es braucht ein product Domain, die Buch spezifische Attribute verwalten kann.
      ​​
      Der Auftrag wäre m.E. genauso sparsam zu implementieren wie das Lager. Er würde nicht selbst im Lager rumwühlen. Das macht ein collect service oder sowas , vielleicht auch verschiedene services, je nach Entwicklungsstand. An der Stelle fängt ja so ein System erst an, Sinn zu ergeben. Die Vorstellung, dass es verschiedene Service Implementierungen zwischen Auftrag und Lager geben soll / kann, die alle dazu führen, dass der Kunde seine Bücher bekommt, alle aber ganz anders funktionieren und sich zwangsläufig mit den definierten Interfaces von Auftrag und Lager begnügen müssen, ist doch sehr plastisch und vielleicht sogar hilfreich beim Design.
      Im Nachhinein betrachtet erscheint mir die Service-Lösung auch am sinnvollsten.

      Aktuell würde ich dem Auftragsaggregat eine Methode geben welche eine ISBN, eine Liste von Bestand-VOs und eine Bestellmenge entgegen nimmt. Anhand dieser Informationen kann das Aggregat dann, unter seinen Regeln, die Auftragsposition erstellen.

      Ein Service würde die passenden Bestände aus dem Lager kramen und an das Auftragsaggregat übergeben.

      Ich stelle gerade fest dass es wenig Sinn macht nur einen Teil des Modells zu betrachten, da die Lagerbestände bestimmten Regeln unterliegen können wann und an wen sie verkauft werden dürfen. Für die Abfrage der Lagerbestände wäre es von daher deutlich leichter wenn ein Repository die Abfrageregeln überwachen könnte. Ansonsten müsste der Service bei jedem Lager einzeln anfragen ob es dort Bestände gibt welche Regel "verfügbar für Kunde X" entsprechen.

      Zitat von Perry Staltic Beitrag anzeigen
      Achso, welche Vorteile siehst Du in dem DDD Verfahren für Deinen Anwendungsfall hier?
      In einem Wort: Struktur. Die aktuelle Lösung ist voll von Seiteneffekten welche kein Entwickler (selbst die welche sich sehr gut mit der Codebasis auskennen) noch überblicken kann. Geschenkt, das wäre mit anderen Vorgehensmodellen sicher auch realisierbar.

      Davon abgesehen erhoffe ich mir durch eine weniger auf Datenmodelle ausgerichtete Herangehensweise eine verbesserte Erweiterbarkeit der Software.

      Außerdem ist es ein großer Vorteil wenn unser Auftraggeber und der Code dieselbe Sprache sprechen (was für das Design der Domänen bei DDD ja praktisch zwingend erforderlich ist). Bei anderen Modellen sehe ich es nicht selten dass der Code eher technische Begriffe verwendet welche mit dem eigentlichem Verhalten nichts zu tun haben.

      Des weiteren halte ich die Einführung eines etablierten Vorgehensmodells für förderlich damit auch das Entwicklerteam, im Bezug auf die Implementierung der Geschäftslogik, die selbe Sprache spricht.
      "Alles im Universum funktioniert, wenn du nur weißt wie du es anwenden musst".

      Kommentar


      • #4
        Moin ich glaube du denkst zu kompliziert und das hier "Gesetzt dem Fall ich überlasse dem Auftragsaggregat das Finden der Bestände könnte ich ein Value-Object erzeugen" sollte man nicht machen. Ein Aggregate ist eine Zusammensfassung aus Entities mit einem Root Entity.

        Also Ein Buch wäre eine Entity, das hat wiederum AuthorenEntities aber das Entity kann nichts finden. Das macht das Repository.

        Mach das nicht zu kompliziert, es bringt dir nichts, die komplette Geschäftslogik von irgendwas abzubilden wenn es gerade auf einer Webseite nicht angezeigt werden soll.

        Du brauchst Prozesse für deine Geschäftslogik. Nenn diese Klassen einfach "UseCase"

        Halte die Klein und schreibe automatisierte Tests für die. Ein UseCase macht im Grunde paar kleinere Dinge wie zum Beispiel Entity aus Repository raussuchen, Entity bearbeiten und es eventuell zurück ins repository legen. Das UseCase hat ein MessageStream objekt, das beinhaltet UserEingaben/Server Daten/Configuration/Cookie und Ausgabewerte. Die UseCases werden nacheinander in einer Action im Controller aufgerufen.

        also für dein Beispiel:

        $orderBookUseCase->process($messageStream);
        $changeOrderedItemsUseCase->process($messageStream);
        $completeOrderUseCase->process($messageStream);

        die UseCases an sich prüfen am Anfang ob die überhaupt ausgeführt werden sollen oder nicht, und wenn nein, wird abgebrochen.

        somit hast du auch Struktur geschaffen. Route führt Controller und Action aus, die Action dann weitere UseCases und jedes UseCase dann für sich eine kleinere Logik. Das MessageStream wird dann anschließend über eine Template Engine Gerendert die Variablen im messageStream wurden innerhalb des UseCases gesetzt.

        Vielleicht ist das dan einfacher zu verstehen

        Viele Grüße
        apt-get install npm -> npm install -g bower -> bower install <package> YOLO [URL]https://www.paypal.me/BlackScorp[/URL] | Mein Youtube PHP Kanal: [url]https://www.youtube.com/c/VitalijMik[/url]

        Kommentar


        • #5
          Danke auch dir für deine Antwort.

          Zitat von BlackScorp Beitrag anzeigen
          Moin ich glaube du denkst zu kompliziert und das hier "Gesetzt dem Fall ich überlasse dem Auftragsaggregat das Finden der Bestände könnte ich ein Value-Object erzeugen" sollte man nicht machen. Ein Aggregate ist eine Zusammensfassung aus Entities mit einem Root Entity.

          Also Ein Buch wäre eine Entity, das hat wiederum AuthorenEntities aber das Entity kann nichts finden. Das macht das Repository.
          Das dafür ein Repository zuständig ist, ist klar. Mit meiner Aussage meinte ich eher das Ansprechend des Repositories durch das Aggregate-Root. Die Option würde aber eine grundlegende Regel verletzen das Aggregate-Roots nicht mit Repositories zu interagieren haben.

          Zitat von BlackScorp Beitrag anzeigen
          Mach das nicht zu kompliziert, es bringt dir nichts, die komplette Geschäftslogik von irgendwas abzubilden wenn es gerade auf einer Webseite nicht angezeigt werden soll.
          Den verstehe ich nicht. Was hat die Darstellung auf der Webseite mit dem Erzwingen der Invarianten des Auftrages und Lagers zu tun?

          Zitat von BlackScorp Beitrag anzeigen
          Du brauchst Prozesse für deine Geschäftslogik. Nenn diese Klassen einfach "UseCase"

          Halte die Klein und schreibe automatisierte Tests für die. Ein UseCase macht im Grunde paar kleinere Dinge wie zum Beispiel Entity aus Repository raussuchen, Entity bearbeiten und es eventuell zurück ins repository legen. Das UseCase hat ein MessageStream objekt, das beinhaltet UserEingaben/Server Daten/Configuration/Cookie und Ausgabewerte. Die UseCases werden nacheinander in einer Action im Controller aufgerufen.

          also für dein Beispiel:

          $orderBookUseCase->process($messageStream);
          $changeOrderedItemsUseCase->process($messageStream);
          $completeOrderUseCase->process($messageStream);

          die UseCases an sich prüfen am Anfang ob die überhaupt ausgeführt werden sollen oder nicht, und wenn nein, wird abgebrochen.

          somit hast du auch Struktur geschaffen. Route führt Controller und Action aus, die Action dann weitere UseCases und jedes UseCase dann für sich eine kleinere Logik. Das MessageStream wird dann anschließend über eine Template Engine Gerendert die Variablen im messageStream wurden innerhalb des UseCases gesetzt.

          Vielleicht ist das dan einfacher zu verstehen
          Vermischen wir hier nicht Domain-Driven-Design und Clean-Architecture?

          Nur um die Begrifflichkeiten klar zu kriegen. Denn aus der DDD-Referenz geht das Konzept "Use case" als Kapselung von Logik jedweder Art nicht hervor.

          Gemäß diesem schönem Artikel https://khalilstemmler.com/articles/...-architecture/ wäre der Use-Case in CA mit einem Application-Service in DDD gleichzusetzen, mit dem Unterschied dass der Application-Service per Definition keine Geschäftslogik erlaubt während diese in CA im Use-Case wohl durchaus zulässig ist.

          Anhand des Artikels wäre der richtige Ort für die Implementierung der Logik, welche mein Problem behandelt, der Domain-Service, was konform mit dem Beitrag von Perry Staltic geht und in der Tat zu recht "sparsamen" Aggregaten führt.

          Der "Use-Case" käme in Form eines Application-Service, welcher ggf. einen oder mehrere Domain-Services aufruft. Auf dein Beispiel bezogen wären $orderBookUseCase, $changeOrderedItemsUseCase und $completeOrderUseCase keine Use-Cases sondern Domain-Services. Im Falle einer Webanwendung könnte der Application-Service die Controller-Action sein, wobei es wohl eher Best-Practice ist dafür Commands zu implementieren welche vom Controller über DTOs gefüttert werden.
          "Alles im Universum funktioniert, wenn du nur weißt wie du es anwenden musst".

          Kommentar


          • #6
            Zitat von Dark Guardian Beitrag anzeigen
            Danke auch dir für deine Antwort.
            Das dafür ein Repository zuständig ist, ist klar. Mit meiner Aussage meinte ich eher das Ansprechend des Repositories durch das Aggregate-Root. Die Option würde aber eine grundlegende Regel verletzen das Aggregate-Roots nicht mit Repositories zu interagieren haben.
            Was meinst du mit Aggregate Root darf nicht mit Repository interagieren? Also ich darf schon das Aggregte aus dem Repository laden und in das Repository hinterlegen, ist doch eine Interaktion

            Zitat von Dark Guardian Beitrag anzeigen
            Den verstehe ich nicht. Was hat die Darstellung auf der Webseite mit dem Erzwingen der Invarianten des Auftrages und Lagers zu tun?
            Weil in PHP Context es darum geht, einen gewissen Inhalt auszuführen bzw einen Prozess abzuarbeiten. Als Beispiel hast du ein Buch Aggregat mit Autoren entities, wenn du die aktuell nicht darstellen musst auf deiner Seite so macht es kein Sinn ein komplettes Objekt mit den Authoren korrekt zu ermitteln. Wenn du ein Titel anzeigen musst, dann zeige den an und ingoriere die Domäne das ein Buch auch ein Autor hat. Darauf will ich hinaus. DDD Klingt im ersten Moment schön, aber eine komplexe Abhänigkeit und Prozessketten und Entity Aggregates brauchst du nicht immer und nicht auf jeder Route.

            Zitat von Dark Guardian Beitrag anzeigen
            Vermischen wir hier nicht Domain-Driven-Design und Clean-Architecture?
            Da ist jetzt kein großer Unterschied.

            Zitat von Dark Guardian Beitrag anzeigen
            Nur um die Begrifflichkeiten klar zu kriegen. Denn aus der DDD-Referenz geht das Konzept "Use case" als Kapselung von Logik jedweder Art nicht hervor.

            Gemäß diesem schönem Artikel https://khalilstemmler.com/articles/...-architecture/ wäre der Use-Case in CA mit einem Application-Service in DDD gleichzusetzen, mit dem Unterschied dass der Application-Service per Definition keine Geschäftslogik erlaubt während diese in CA im Use-Case wohl durchaus zulässig ist.

            Anhand des Artikels wäre der richtige Ort für die Implementierung der Logik, welche mein Problem behandelt, der Domain-Service, was konform mit dem Beitrag von Perry Staltic geht und in der Tat zu recht "sparsamen" Aggregaten führt.

            Der "Use-Case" käme in Form eines Application-Service, welcher ggf. einen oder mehrere Domain-Services aufruft. Auf dein Beispiel bezogen wären $orderBookUseCase, $changeOrderedItemsUseCase und $completeOrderUseCase keine Use-Cases sondern Domain-Services. Im Falle einer Webanwendung könnte der Application-Service die Controller-Action sein, wobei es wohl eher Best-Practice ist dafür Commands zu implementieren welche vom Controller über DTOs gefüttert werden.
            Du darfst nicht vergessen das all die schönen Beispiele in den Bücher mit einer Compiler Sprache programmiert sind, bei dem eine Software auch ein State hat und du nicht alle Daten neu laden musst bei jedem Request. In einem HTTP Context musst ein bisschne anders denken.

            Es ist nur so dass deine Beschreibungen sehr nach Theorie klingen. Bei einem HTTP Request hast du eine URL, die holt sich services über einen DI Container / Fascades, es wird nach einer Reihe(sei es in Form von Commands) abgearbeitet und ein Resopnse generiert. Du hast kein State und du kannst es dir nicht leisten alles zur Laufzeit zu laden und komplexe Aggregates abbilden.

            apt-get install npm -> npm install -g bower -> bower install <package> YOLO [URL]https://www.paypal.me/BlackScorp[/URL] | Mein Youtube PHP Kanal: [url]https://www.youtube.com/c/VitalijMik[/url]

            Kommentar

            Lädt...
            X