Der nachfolgende Text wurden mit KI erstellt und kann Fehler enthalten. Fehler gefunden? Bei GitHub editieren

Einführung und Trainingshinweis

Ich habe noch einen Hinweis.

Es gibt dieses Training Domain-Driven Design saniert Legacy.

Training Details

Das ist ein Nachmittag am 16.12. über Socratoria.

Das ist auch nicht besonders teuer.

Wenn euch also dieses Thema mit Domain-Driven Design interessiert, insbesondere der Umgang mit Legacy, das ist eine Möglichkeit, darüber nochmal was mit bei mir tatsächlich ganz praktisch zu erfahren.

Praktischer Fokus

Also wir machen da praktische Übungen und reden halt nicht nur darüber, sondern machen es eben auch.

Und das ist nochmal, glaube ich, eine gute Ergänzung für das, was wir hier sehen.

Weil wie gesagt, hier sehen wir eben nicht, wie man mit Legacy umgeht.

Taktisches Design

Gut, das Erste, worüber ich tatsächlich sprechen möchte, ist das Thema mit dem taktischen Design.

Das ist auch so ein bisschen so die Bruchstelle zum letzten Mal.

Wir haben letztes Mal sehr grobgranular über das Ganze gesprochen.

Also, wie teile ich ein System auf in bauende Kontexte?

Wie baue ich irgendwie nicht?

Welche Zuständigkeiten haben Teams und so weiter und so fort?

Und das ist hier jetzt eine ganz andere Ebene.

Das ist die Ebene von so tatsächlich Klassen.

Und warum jetzt taktisches Design?

Taktisches Design sind ein paar objektorientierte Konzepte.

Und es macht total viel Sinn, gute objektorientierte Systeme zu bauen.

Dafür hilft genau taktisches Design.

Und die Patterns können wir jetzt durchgehen.

Entities

Das erste Pattern ist eine Entity.

Das ist irgendetwas, was eine Identität hat.

Also eine Person hat eine Identität.

Das heißt, wenn es jetzt den Eberhard Wolf nochmal gebe.

Da gibt es jetzt eine vollständig identische Kopie, die also so aussieht, wie ich, die da wohnt, wo ich wohne und nicht so weit identisch ist.

Das ist trotzdem eine andere Person.

Die kann sich irgendwie unterscheiden.

Und das bedeutet, dass hier eben tatsächlich die Dinger eine Identität haben.

Eine Person hat eine Identität.

Ein Produkt hat eine Identität.

Solche Geschichten halt.

Und davon grenzen sich ab so was wie Value Objects.

Value Objects

Also zum Beispiel einen Wert wie zwei Euro oder zwei Meter.

Zwei Meter sind halt zwei Meter.

Und es ist relativ egal, ob diese zwei Euro sind zwei Euro.

Das heißt also, das sind tatsächlich Dinge, die eben nur sich durch den Wert unterscheiden.

Dann gibt es Domäne Events.

Domain Events

Das sind Sachen, die halt bei Domänen Expertinnen für die halt irgendwie relevant sind.

Also irgendwas ist passiert in der Vergangenheit.

Darüber hatten wir ja letztes Mal schon gesprochen.

Das ist auch etwas, was bei Event Storming beispielsweise eine Rolle spielt.

Das sind also Dinge, die ich da jetzt irgendwie auch als Klassen umsetzen kann.

So vielleicht noch zwei generelle Dinge.

Abhängigkeiten

Der eine generelle Hinweis ist, dass was wir hier sehen, ist jetzt so, dass wir im Wesentlichen Abhängigkeiten von oben nach unten haben.

Das heißt, das hier sind tatsächlich Basis Dinge, die halt von nichts anderem abhängen.

Und die andere Sache ist, wir reden halt über ein richtiges objektorientiertes Konzept.

Das heißt, die Dinge, die wir hier sehen, haben Logik.

Also wenn ich jetzt sage, ich kann Geldbeträge zum Beispiel summieren und ich kann die versuchen zu runden und was auch immer ich da sonst noch machen kann.

Das sind halt Dinge, die ich jetzt an diesem Value Object beispielsweise modellieren würde.

Das sind also Dinge, die tatsächlich Logik enthalten.

Darauf aufbauend gibt es dann auf der nächsten Schicht diese Aggregates.

Aggregates

Die sind so, dass sie aus mehreren verschiedenen Dingen zusammengesetzt sind.

Deswegen sind sie auch etwas höher.

Das heißt, es gibt Entities und Value Objects, die sind in Aggregates zusammengefasst.

Und da gibt es eine Aggregate Route, die jetzt dafür zuständig ist, dort die Konsistenz zu gewährleisten.

Das heißt, wenn ich jetzt zum Beispiel eine Bestellung habe und ich füge in diese Bestellung irgendetwas ein, was ich gerne bestellen möchte, dann wird das zum einen eingefügt und zum anderen wird dafür gesorgt, dass eben der Gesamtbetrag der Bestellung entsprechend angepasst wird.

Dann ist das Ding halt in sich konsistent und das Aggregate Route sorgt dann eben dafür, dass genau diese Änderung sozusagen automatisch stattfindet.

Konsistenz

Was eben bedeutet, dass innerhalb von Aggregates Konsistenzbedingungen synchron durchgesetzt werden. Über Aggregate Grenzen hinweg nicht unbedingt synchron und ich kann Aggregates auch benutzen, um sie im Netzwerk zu replizieren.

Dann habe ich da auch Eventual Consistency, das heißt also alle Kopien des Aggregates werden dann schlussendlich, Eventual bedeutet schlussendlich, nicht eventuell, sondern schlussendlich werden die halt irgendwie konsistent werden.

Das ist deswegen interessant, weil eine Überweisung beispielsweise ändert zwei Konten.

Jetzt könnte man denken, na das will ich jetzt irgendwie konsistent haben und was Domain-Design sagt, ist das Konto in sich ist konsistent.

Da wäre also jetzt zum einen eben der Betrag richtig.

Ich habe irgendwie nach der Überweisung 240 Euro und zum anderen ist die Überweisung da tatsächlich auch verbucht oder nichts von beiden.

Aber die Konten, die können irgendwie dann inkonsistent sein.

Was übrigens auch genauso ist, wie die Realität aussieht.

Also wenn ich jetzt Geld überweise, ist das für einige Zeit irgendwo zwischen diesen beiden Konten.

Also das Geld ist einige Zeit verloren.

So nehme ich zumindest an, das ist also das, was hier irgendwie sozusagen eine Rolle spielt.

Was hier auch bedeutet, dass Bauen und Kontexte, die ja grobgranularer sind, nicht etwa in sich gesamtkonsistent sind, sondern eben aggregates-konsistent sind.

Das besteht eben aus Entities, also meine einzelnen Buchungen wären eben beispielsweise irgendwelche Entities und Wertobjekten, der Kontostand beispielsweise wäre so ein Wertobjekt.

Repositories

Dann habe ich Repositories, die geben mir die Illusion einer Kollektion von Aggregates, die dann im Speicher scheinbar steht, aber in Wirklichkeit in der Datenbank ist.

Das ist halt auch ein konzeptionelles Ding und was ich da besonders spannend finde, ist, dass Repositories eben auch die richtigen, fachlich richtigen Anfragen implementieren.

Das heißt also, ein Repository wird nicht sagen, ich gebe dir Zugriff auf irgendwelche beliebigen Objekte, sondern das Repository wird sagen, okay, ich habe jetzt eben die Möglichkeit, ein Konto nach zum Beispiel Kontonummer zu finden, vielleicht auch nach dem Namen des Kontoinhabers und dem Geburtsdatum, aber nicht anhand des Kontostandes, weil das irgendwie fachlich überhaupt keinen Sinn macht.

Das würde jetzt bedeuten, dass das Repository nur die fachlich sinnvollen Anfragen exponiert, was ein ganz interessantes Designkonzept ist, weil so etwas wie zum Beispiel GraphQL sagt, ich kann beliebige Anfragen stellen, Repository sagt, das macht eigentlich keinen Sinn, wir wollen nur die fachlich korrekten Anforderungen, eher Anfragen möglich haben.

Dann gibt es Factories.

Factories erzeugen komplexe Value Objects oder Aggregates.

Sehr umgänglich auf For-Pattern braucht man nicht viel drüber zu sagen.

Wenn ein Konstruktor nicht reich ist, benutze ich eine Factory und darüber sind dann Services.

Services sind die Dinge, wo ich geschäftslogisch nicht sinnvollerweise in einem Aggregate oder Entities unterbringen kann.

Es gibt Geschäftslogik, die übergreifend ist über verschiedene Aggregates.

Die Überweisung beispielsweise ist etwas, wo ich zwei Aggregates habe.

Jetzt kann ich schlecht einem Konto zuordnen, also baue ich das wahrscheinlich in ein Service.

Damit ist klar, wie das funktioniert.

Ich habe Logik, die bringe ich in irgendeiner dieser Klassen unter und es ist nicht so, dass Entities logikfrei sein sollten.

Jetzt ist die Frage, das ist etwas, was sich insgesamt durch diesen Talk so zieht.

Ich finde es immer wichtig, Alternativen zu benennen.

Wir haben jetzt taktisches Domain-Driven Design und jetzt ist die Frage, ob man taktisches Domain-Driven Design umsetzen kann.

Eine Möglichkeit ist, das funktional zu machen.

Ich habe einen seiteneffektfreien, funktionalen Kern.

Das heißt, ich habe keine Entities, ich habe keine Aggregates.

Wenn ich ein Entity ändere, dann hat das einen Seiteneffekt.

Das Ding hat ja einen Zustand.

Das Konto hat einen Kontostand beispielsweise und das bedeutet, dieser Zustand ändert sich.

Das ist offensichtlich ein Seiteneffekt oder eine Wirkung.

Bei funktionaler Programmierung kann ich so etwas nicht haben.

Bei funktionaler Programmierung hätte ich einen Kern, der sagt, ich berechne aus einem Zinssatz den neuen Kontostand, aber es ist dann die Aufgabe des Systems rundherum dafür zu sorgen, dass dieser Seiteneffekt erzeugt wird, dass diese Information gespeichert wird.

Ich hätte einen Kern, der sagt, das ist der neue Kontostand und dann hätte ich irgendwo um diesen Kern herum etwas, was dafür sorgt, dass das System tatsächlich in die Datenbank gespeichert wird. Über das Thema habe ich gesprochen mit Mike Sperber vor einiger Zeit.

Tatsächlich ist das schon mehr als ein Jahr her.

Da haben wir darüber diskutiert, wie eine funktionale Architektur aussehen soll.

Mike ist jemand, der sich sehr stark damit beschäftigt hat.

Da haben wir uns die Beispielaufgabe angeguckt.

Da kann man also über dieses Thema genau reden.

Was es noch an Alternativen gibt, ist, wenn ich weniger komplexe Systeme habe.

Die Prämisse für dieses Set von Patterns ist, ich habe komplexe Geschäftslogik.

Und wenn ich jetzt ein System habe, das Daten von A nach B kopiert und ich baue jetzt einen Repository und Entities und Value Objects und so weiter, dann ist die Frage, ob das wirklich schlau ist.

Ich investiere viel Aufwand, diese Sachen zu bauen, aber den Vorteil, dass ich dort eben objektorientierte Möglichkeiten habe, meine Logik zu strukturieren, davon habe ich da ja wenig, weil ich habe eben keine Logik.

Dafür ist eine Alternative sowas wie ein Transaction Script.

Ein Transaction Script sagt, ich bearbeite einen Request aus der Präsentationsschicht direkt und habe da auch Datenbank-Code drin.

Das heißt, ich habe einen HTTP-Request, der in mein System reinkommt und dann sage ich, insert into meine tolle Datenbank-Tabelle irgendeinen Wert.

So etwas ähnliches ist ein Table Model, wo ich sage, es gibt ein Objekt, was eine Datenbank-Tabelle repräsentiert.

Das hat den Vorteil, dass ich all diese Schichten nicht habe und relativ problemlos sagen kann, wenn das von der HTTP-Schicht kommt, dann ändere ich genau das in der Datenbank und das sehe ich auf einen Blick.

Das geht dann natürlich massiv schief, wenn ich dort eine komplexe Logik habe, weil die müsste ich da jetzt irgendwie rein friemeln und das macht so ein bisschen wenig Sinn.

Hier habe ich zum Beispiel auch den Vorteil, dass ich jetzt die Datenbank-Anfragen zum Beispiel optimieren kann.

Ich kann jetzt handoptimierte Queries da irgendwie reinbauen und irgendwelche tollen Dinge da konstruieren, ohne dass ich da viel Aufwand habe.

Gehen wir mal das Beispiel durch.

Also das Beispiel, was wir hier hatten, war so ein E-Commerce-System.

Das heißt, wir würden uns jetzt irgendeinen von diesem Bauen und Kontexten rausnehmen.

Ich habe mir jetzt den Bauen und Kontext rausgenommen mit der Delivery.

Das ist also das Ding, was sagt, okay, ich möchte den Klicker verschicken.

Wie verschicke ich denn nun tatsächlich diesen Klicker?

Oder wenn ich etwas billig finde, das iPhone, das ist sogar noch teurer, das will ich vielleicht irgendwie nochmal anders versichert verschicken und so weiter und so weiter.

Das ist eben das, was ich hier jetzt entscheiden möchte.

Das heißt, ich habe irgendwelche Logik und fange jetzt an und sage, okay, ich habe also hier eine Entity, also ich habe ein Paket.

Ich habe eine Adresse, das ist ein Wertobjekt, also die Simon von Utrechtstraße, wo die Swaglab GmbH sitzt.

Das ist eben ein Wert, das ist keine Identität in diesem Falle und das zusammen habe ich in so einer Lieferung.

Das heißt also, in dieser Lieferung steht jetzt drin, dieses Paket geht an diese Adresse.

Vielleicht sind da auch noch solche Informationen drin, wie welche Waren sind denn eigentlich in dem jeweiligen Paket drin.

Das ist so, nehme ich jetzt an, kompliziert zu bauen, dass ich dafür eine Factory brauche und ich habe auch so ein Delivery Repository, wo ich also sagen kann, gib mir mal alle Pakete für diesen Kunden.

Das wäre etwas, was ich vielleicht fachlich motivieren kann, die ich gerne in diesem Wagen haben möchte.

Das wäre auch etwas, was ich fachlich motivieren kann und so weiter.

Alexander schreibt gerade, ich bin ein großer Fan von DDD und ich finde, dass viel zu wenig darüber gesprochen wird, wo man DDD nicht einsetzen sollte.

Gut, dass es hier erwähnt wurde.

Genau, vielen Dank.

Andere Sacrifices, was ich jetzt haben kann, ist zum Beispiel ein Kunde und da hätte ich jetzt auch irgendwelche Informationen.

Vielleicht hat er mir ein Set von Adressen, wo er beispielsweise Sachen hinschickt oder was auch immer.

Dann hätte ich hier ein Service, der jetzt sagt, okay, ich schätze eben eine Lieferung.

Ich sage, für diesen Kunden möchte ich gerne diese Lieferung haben und das bedeutet, dieses Ding sorgt dann dafür, dass diese Lieferungen entstehen, braucht Zugriff auf den Kunden.

Das kann ich also schwer dem Kunden oder der Lieferung geben.

Die Lieferung hätte jetzt solche Sachen wie dein Wert ist, ob du versichert bist, solche Geschichten.

Diese übergreifende Logik wäre jetzt eben in so einem Service.

Ich habe hier dennoch so ein Event.

Die Lieferung ist irgendwie geschedult worden.

Das heißt, ich habe jetzt gesagt, das und das soll dann und dann geliefert werden.

Darauf kann jetzt irgendjemand reagieren, wenn diese Person Lust dazu hat.

Ich habe darüber eine ganze Folge gehabt, wo ich das nochmal ausdiskutiert habe.

Die ist jetzt tatsächlich von diesem Jahr.

Da spreche ich tatsächlich eine ganze Stunde über diese ganzen Konzepte und gehe da nochmal tiefer drauf ein.

Die Frage ist, wie ich das umsetze.

Und in gewisser Weise ist das, was wir jetzt hier diskutieren, so plain old Java Objects.

Das ist eigentlich nur ein objektorientiertes Konstrukt, um irgendwie Logik aufzuteilen.

Und deswegen könnte das genug sein.

Was das bedeutet, ist, ich habe hier keine besonderen technischen Anforderungen.

Also Entities will ich vielleicht noch speichern.

Das heißt, da habe ich vielleicht so etwas, dass ich eine Persistenzlösung haben möchte.

Vielleicht ist in den Repositories auch eine Persistenzlösung drin, die das konkret umsetzt.

Aber im Wesentlichen habe ich hier eigentlich einfach nur ein objektorientiertes System.

Und es gibt sowas wie zum Beispiel X-Molecules, was verschiedene Programmiersprachen, die die Konzepte sozusagen direkt unterstützt.

Und das hat der gute Oliver Rothbohm auch beschrieben in einem Blogpost.

Und darüber haben wir dann irgendwie auch gesprochen.

Das heißt, ich kann mit sowas wie J-Molecules jetzt sehr schön so ein System aufbauen.

Und dabei geht es zum Beispiel auch um Abhängigkeiten.

Also ich möchte jetzt gerne zum Beispiel, dass ein Service Aggregates nutzen kann.

Aber Aggregates sollten halt keine Services nutzen.

Das heißt, es gibt halt so Beziehungen, die sich halt aus diesem Patternzeit ziemlich direkt ergeben.

Und das bedeutet also, dass ich da eigentlich eben diese Abhängigkeiten habe, wie zum Beispiel, dass Services Aggregates benutzen dürfen.

Und da kann ich so Architekturmanagement-Werkzeuge verwenden.

Also die Architekturmanagement-Werkzeuge.

Darüber habe ich halt tatsächlich mehrere Episoden gehabt.

Ich verlinke das übrigens auch alles noch in den Shownotes.

Das sind so Werkzeuge, mit denen ich irgendwie sagen kann, ok, Services dürfen auf Aggregates zugreifen.

Aggregates dürfen auf Entities und Value Objects zugreifen und so weiter und so weiter.

Und dann werden eben diese Abhängigkeiten sozusagen durchgesetzt.

Das heißt also, ich habe einen Checker, der da drüber läuft und sagt, nee, das darfst du nicht.

Und da gibt es ganz unterschiedliche Werkzeuge.

Also es gibt sowas wie Structure 101 oder so eine Graph, die das eher grafisch darstellen, die ich auch in den Bildprozess mit reinbekommen kann.

Es gibt zum Beispiel sowas wie ArcUnit, wo ich das als Teil des Unit-Tests habe.

Und wie gesagt, da gibt es eine große Auswahl und man kann sich da diese ganzen Werkzeuge sozusagen anschauen.

So, in dem Kontext möchte ich nochmal sprechen über das Thema Design-Level-Event-Storming.

Wir haben gesprochen über Big-Picture-Event-Storming bei der letzten Session und Big-Picture-Event-Storming ist letztendlich sowas, dass ich eben Domäne-Events habe.

Und diese Domäne-Events, die schreibe ich halt irgendwie auf und kriege dadurch halt eine Information darüber, wie meine Domäne so im Wesentlichen funktioniert.

Und ich kann das benutzen, um zum Beispiel Baune Kontexte zu identifizieren.

Das heißt, ich habe solche Geschichten dort typischerweise wie, ich habe eine Bestellung entgegengenommen, das Ding ist jetzt ausgeliefert, solche Sachen.

Und dann kann ich halt irgendwie sagen, bestimmte Funktionalitäten, also das Ausliefern und das Rechnungsschreiben ist vielleicht in unterschiedlichen Baune Kontexten.

Darüber haben wir letztes Mal gesprochen.

So, JudeRude schreibt, sollten die Value-Objects eigentlich nicht immutable sein und damit keine Logik haben?

Genau, die repräsentieren Werte und Werte sind unveränderlich.

Zwei Euro sind halt zwei Euro.

Ich kann nicht sagen, zwei Euro sind plötzlich zwei Euro und zehn Cent, sondern das ist eben ein Wert.

Was halt bedeutet, wenn ich jetzt zum Beispiel den Wert meiner Bestellung zurück und das alte Value Object ist dann eben auch noch da, sprich die sind dann eben unänderbar, also sie haben halt irgendwie Wertsemantik und auch sonst, also wie soll ich sagen, solche Sachen wie Vergleiche sind da vielleicht auch ein Thema, nicht?

Also wenn ich jetzt Meter vergleiche oder mich Geldbeträge vergleiche, vielleicht ist das irgendwie etwas, was ich da auch drin habe als Logik.

Von daher haben die halt auch Logik, das ist genau die Idee.

Christian Trutz schreibt, verwendest du zum Beispiel Spring Modulist, um Packages zu strukturieren?

Genau, sowas kann man beispielsweise dann verwenden, um so ein System halt auf dieser Package-Ebene zu strukturieren, genau.

Gut, ich glaube, dann haben wir das Thema so ein bisschen erledigt.

Ich schaue noch mal kurz, ob hier noch was reinkommt, denn sonst würde ich tatsächlich mit diesem Design-Level-Event-Storming weitermachen.

Wichtig ist bei dem Design-Level-Event-Storming sich noch mal zu vergegenwärtigen, worum es eigentlich geht.

Ich möchte gerne verstehen, wie die Geschäftslogik ist und das will ich halt bei diesem Big-Picture-Event-Storming, was wir beim letzten Mal diskutiert haben, grobgranular verstehen und hier möchte ich es jetzt feingranular verstehen.

Das heißt also, ich habe zum Beispiel ein Read-Modell und ein Read-Modell ist etwas, wo ich jetzt zum Beispiel eine UI rankleben kann und sagen kann, okay, die zeigt halt irgendetwas an.

Dann habe ich irgendwelche Aktoren, also Menschen, die irgendwas machen und zum Beispiel ein Command rausgeben können und entweder durch ein externes System oder durch mein eigenes System entstehen dann irgendwelche Events.

Also ich könnte zum Beispiel sagen, ich bin ein Kunde, ich bin also hier in Ekta, ich gebe das Command, ich will es jetzt gerne haben, also ich möchte gerne diese Bestellung abgeben.

Das geht an mein System, Event, was irgendwie rauskommt, ist Bestellung akzeptiert.

Dann könnte es halt irgendwie sein, dass ich daraus dann eine Bestellung generiert habe, die ich mir anschließend anzeigen kann und hier wäre jetzt vielleicht so eine Policy, die jetzt sagt, es entsteht das nächste Command.

Das ist so ein Automatismus, das heißt, wenn hier die Bestellung akzeptiert worden ist, könnte ich eine Policy haben, die jetzt sagt, okay, alles klar, dann werde ich jetzt irgendwie dafür sorgen, schreibt bitte eine Rechnung und sorge bitte dafür, dass das Ding auch ausgeliefert wird.

Das ist so die Idee und dadurch komme ich jetzt in ein feingranulares Modell.

Also ich hätte halt in dem Big Picture Event Storming, was wir letztes Mal diskutiert haben, hätte ich halt nur das hier, also die Domain Events.

Hier habe ich jetzt noch viele, viele zusätzliche Informationen, die mir jetzt ein tieferes, besseres Verständnis darüber geben, was hier eigentlich passiert.

Und ich erinnere noch mal daran, das ist eben etwas, was wir jetzt mit Domänexpertinnen zusammen erstellen wollen und das Ziel ist also, die Domäne jetzt auf diesem Detail-Level, das ich für ein taktisches Domain-Driven-Design benötige, dass ich das eben auf dieser Ebene verstehe und da ist jetzt kein, also ich würde sagen, offensichtlich gibt es kein triviales Mapping auf taktisches Domain-Driven-Design.

Sprich, das, was wir hier jetzt sehen, ist eine ganz andere Begriffswelt.

Also wir reden über Systeme, wir reden über Read Models, wir reden halt über UI.

Das sind alles Dinge, die in diesem taktischen Domain-Driven-Design nicht vorkommen.

Deswegen ist es vielleicht ganz spannend, noch mal zu schauen, wie ich das jetzt eigentlich mappe.

Und ich würde ein Command in taktischem Domain-Driven-Design, würde ich erwarten, ist ein Methodenaufruf auf einem Aggregate oder auf einem Service.

Wenn ich also jetzt sage, ich möchte das gerne bestellen, dann würde ich halt erwarten, es gibt irgendwo eine Methode oder vielleicht auch mehrere Methoden.

Eine Methode würde ich sogar sagen, die jetzt sagt, okay, ich bestelle jetzt dieses ganze Graffel für dich und die ist jetzt in dem Fall wahrscheinlich in einem Service, weil ich im Rahmen dessen eine Bestellung erzeugen muss und verschiedene andere Dinge erzeugen muss.

Könnte aber für einfache Fälle auch in einem Aggregate sein.

So, dann habe ich eben das System, das wäre jetzt mein Aggregate oder mein Service, wenn ich das Frank Granada sehe.

Da kommt jetzt ein Domänen-Event raus, das könnte ich direkt als Domänen-Event auch im taktischen Domain-Driven-Design umsetzen.

Das ist also etwas, wo tatsächlich diese Begrifflichkeit letztendlich identisch ist.

Dann habe ich ein Read-Modell und das ist letztendlich wieder ein Aggregate.

Das heißt, das Ding, wo ich hier das Command aufrufe und das Ding, was ich hier anzeige, kann aus meiner Sicht dasselbe Objekt sein.

In dem einen Fall benutze ich halt eben die Lese-Geschichten und in dem anderen Fall die Schreibt-Geschichten.

Das ist, glaube ich, eine gute Praxis, die Methoden, die Zustand ändern, zu trennen von den Methoden, die mir eine Information über den Zustand geben.

Also, ich habe entweder Methoden, die sagen, ich sage dir irgendwas über die Lieferung, was ist da drin an Produkten oder so.

Oder ich habe Methoden, die dafür sorgen, dass irgendetwas mit dieser Lieferung passiert und dieses Read-Modell wäre dann der Teil des Aggregates, der halt tatsächlich diese Auskunft über diesen Zustand gibt.

Wir sprechen gleich noch über solche Sachen wie CQS.

Da würden wir tatsächlich jetzt das Read-Modell auch trennen und eben sagen, das sind andere Klassen, vielleicht sogar ein anderer Microservice.

Das ist eine Option.

Ich halte das persönlich nicht für zwingend und hier sieht man eben genau eine der Möglichkeiten, die ich jetzt habe.

Ich kann mich jetzt also irgendwie als Architekt hinsetzen und kann sagen, okay, wir haben einen relativ einfachen Fall.

Wir packen eben die Commands und das Read-Modell zusammen oder ich kann sagen, aus irgendwelchen Gründen ist es halt kompliziert und ich will das eher trennen.

Vielleicht will ich die Trend skalieren, whatever, dann mache ich ja CQS.

Das ist aber nicht zwingend.

Ich will hier ja nur verstehen, was die Menschen wollen und dafür ist es vielleicht ganz interessant zu sehen, was ich hier eigentlich lesen möchte und genau die Information bekomme ich aus dem Design-Level-Events-Tomic raus.

Eine Policy wäre jetzt auch wieder etwas, wo auf ein Domain-Event reagiert wird, also irgendwelche Geschäftslogik, die dafür sorgt, dass zum Beispiel diese Rechnung geschrieben wird.

Das ist etwas, was eben auch wieder vielleicht in einem Aggregate oder in einem Service dann passiert, da wo eben Geschäftslogik typischerweise ist.

Damit bin ich eigentlich mit dem Thema sozusagen soweit erst mal durch und kann weitermachen mit dem nächsten Thema.

Das ist diese Geschichte mit Events Sourcing.

Hier ist ein weiteres Thema.

Events Sourcing und CQS sind Möglichkeiten, wie ich bestimmte Dinge einfach umsetze und die sind unabhängig von taktischem Domain-Design.

Ich werde glaube ich auch gleich noch mal sagen, wie ich dieses Thema insgesamt bewerte, also ob das zwingend ist oder wie auch immer.

Event Sourcing

Events Sourcing bedeutet, dass ich die Events, die zu einem bestimmten Zustand geführt haben, auch speichere.

Ich kann auch den Zustand selber speichern.

Offensichtlich muss ich das nicht unbedingt, weil ich kann aus den Events diesen Zustand ermitteln.

Ich kann jetzt als System of Record, also das System, das tatsächlich jetzt das Verlässliche sein soll, da kann ich entweder sagen, das ist der Zustand oder die Events.

Das bleibt mir dann überlassen.

Das heißt, was ich jetzt machen kann, zum Beispiel mit irgendwelchen Bestellungen, ist, ich habe hier einen Event Store und ich habe hier den Zustand.

Jetzt habe ich hier eine Schnittstelle und irgendwelche Aufrufe, irgendwelche Nachrichten passen auf mich ein.

Ich rede also jetzt über ein System, was sich damit beschäftigt, irgendwas mit Bestellungen zu machen.

Hier kommt ein Aufruf, der sagt, diese Bestellung 42 soll jetzt ausgeliefert werden.

Das heißt, ich merke mir in dem Event Store, dass diese Bestellung 42 tatsächlich ausgeliefert werden soll und akzeptiert ist.

Dann habe ich hier den Zustand, der jetzt sagt, die Bestellung 42 gibt es.

Dann kommt der nächste Aufruf, der sagt, Bestellung 23 ist irgendwie da.

Dann habe ich hier als Zustand eben auch die Bestellung 23.

Dann sage ich als nächstes, die Bestellung 42 ist gecancelt.

Das ist wieder ein Event.

Das merke ich mir jetzt wieder im Zustand und dann sage ich, die andere ist irgendwie ausgeliefert und das merke ich mir auch wieder im Zustand.

Das heißt also, ich habe hier jetzt ein System, was den Zustand speichert und außerdem die Events, die zu diesem Zustand geführt haben.

Bei einer Bestellung bin ich nicht sicher, ob das so wirklich notwendig ist.

Bei einem Konto bin ich mir relativ sicher, dass das wirklich unbedingt notwendig ist, denn mich interessiert nicht nur, wie viel Geld ich gerade auf dem Konto habe, sondern auch, ob die letzten Überweisungen mir eingegangen sind.

Das führt dazu, dass wir hier Systeme unterscheiden können.

Wir haben Systeme, die nur den Zustand abspeichern.

Ich könnte jetzt ein System haben, das sagt, die Bestellung 42 ist ausgeliefert und ich könnte den Zustand on the fly errechnen.

Ich könnte zum Beispiel den Kontostand ermitteln aus den Überweisungen und Transaktionen, die es bisher gab.

Oder ich kann den Kontostand speichern und außerdem die Events, die dazu geführt haben.

Und wenn ich diese Events speichere, dann ist es eben Event Sourcing.

Da gibt es noch einen Hinweis, der mir wichtig ist.

Persistenzstrategie

Das ist eigentlich im Kern eine Persistenzstrategie.

Das heißt, die Frage ist, wie speichere ich mein Konto bzw. meine Bestellung und ich kann jetzt sagen, ich speichere den Zustand und außerdem die Events, die dazu geführt haben.

Eine Persistenzstrategie sollte intern sein.

Das heißt also, wer auch immer jetzt zuständig ist für sowas wie Konten oder was auch immer ich da habe, sollte eben wählen, ob sie Events Sourcing machen oder nicht.

Das bedeutet insbesondere, dass es nicht so sein sollte, dass die extern beobachtbaren Events dazu führen, dass ich meinen lokalen Zustand habe.

Da gibt es tatsächlich ein Video, wo ich gesagt habe, wenn ich in Kafka alle Events ablege und dann irgendwie sage, ich kann den lokalen Zustand von Microservices aus diesen zentralen Events im Kafka auch für die Kommunikation genutzt werden.

Ich kann diesen Zustand der Microservices rekonstruieren.

Dann habe ich eigentlich einen Datenbankmonolithen.

Die Microservices haben keine unabhängigen Datenmodelle, sondern die haben nur einen Cache für das, was eben in Kafka drin liegt.

Ich kann denen halt ihre Datenbank wegziehen, dann sagen die kein Problem, ich lese nochmal alle Events, ich habe das halt rekonstruiert.

Das muss dazu führen, dass ich dort eine hohe Abhängigkeit habe.

Das ist etwas, was ich eigentlich gerne vermeiden möchte.

Deswegen finde ich das schwierig.

Ich verlinke dann nochmal das Video.

Es gibt dann noch eine andere Geschichte.

Da muss ich nochmal den Artikel rausfinden.

Christian Städtler hat einen Artikel darüber geschrieben, dass diese internen Events möglicherweise feingranularer sind, als das, was ich halt extern bekannt geben möchte.

Wenn ich sage, ich bin dafür zuständig, dass sich eine Kundin registriert, dann habe ich da solche Geschichten wie, deine E-Mail-Adresse war nicht valide, dein Personalausweischeck hat nicht funktioniert.

Das sind alles Dinge, die mich extern nicht interessieren.

Da will ich nur sagen, dieser Mensch ist jetzt validiert.

Da ist auch wieder die Geschichte, die internen Events sind etwas anderes als die externen Events.

Deswegen will ich das trennen.

Was bedeutet das jetzt?

Ich habe hier eine Lieferung.

Die Lieferung ist geschedult.

Die Lieferung ist aus dem Lager rausgenommen worden, in einen Lastwagen reingepackt und ausgeliefert worden.

Die Kundin hat gesagt, habe ich auch bekommen.

Jetzt ist die Frage, ob ich diese Lieferung oder einen Event Store modellieren kann.

Wahrscheinlich nicht, weil das mich genau interessiert.

Mich interessiert, wer wann was damit gemacht hat, weil ich dann später sagen kann, okay, du hast aber gesagt, das ist angekommen oder das ist bei dir im Lastwagen gewesen.

Wo ist das hinbekommen?

Umgekehrt ist es aber eben so, dass ich mir bei dieser Lieferung nicht sicher bin, ob ich den Zustand der Lieferung berechnen möchte.

Was habe ich jetzt davon, wenn ich den Zustand nicht speichere?

Ich würde jetzt neben dem Event Store noch reinschreiben, diese Lieferung ist angekommen und Kundin hat gesagt, ist alles fein.

Ich habe einen Zustand, der hat gesagt, ist erfolgreich abgeschlossen fertig.

Ich kann dann auf der anderen Seite, wenn jemand sich dafür interessiert, wie es dazu gekommen ist, diese Events rausziehen und kann die anzeigen.

Das ist eine fachliche Diskussion.

Eigentlich sage ich, das ist die Art und Weise, wie ich so etwas fachlich typischerweise modellieren möchte.

Das ist etwas, wo ich eben gerade nicht den Zustand rekonstruieren möchte, was häufig ein bisschen als Vorteil verkauft wird von Events.

CQRS, das ist der andere Ansatz.

CQRS

Das ist diese Command Query Responsibility Separation, wo ich also Lesen und Schreiben separiere.

Implementierung

Das würde jetzt so funktionieren, dass ich eine Command Queue habe.

Da sind also irgendwelche Commands.

Ich erzeuge z.B. eine Rechnung.

Dann habe ich einen Command Händler, der z.B. dafür sorgt, dass, wenn eine Rechnung erzeugt wird, auch gleich bezahlt wird.

Dann ist da eine Datenbank.

Da werden die Rechnungen z.B. hinterlegt.

Dann gibt es einen Query Händler, und der ist irgendwie getrennt.

Da kann ich z.B.

Rechnungen lesen.

Die Commands habe ich jetzt vielleicht noch in einem Command Store liegen.

Das wäre jetzt insgesamt mein Modell.

Jetzt hätte ich hier also tatsächlich dieses Read Modell.

Das wäre das, was der Query Händler benutzen würde.

Und hier hätte ich das Modell, wo ich tatsächlich Dinge halt ändere oder diesen Teil, was nicht das System ist, was eben Commands entgegennimmt aus dem Design Level Events Storming.

Wenn ich das so baue, bedeutet das, dass das Modell zum Schreiben hier durch die Commands und das von den Queries eigentlich dasselbe ist, weil das ist eine gemeinsame Datenbank.

Habe ich dann überhaupt was davon?

Ja, ich habe etwas davon, weil diese Query Händler z.B. ein Read Replica benutzen können.

Ich kann jetzt sagen, ich baue das System so, wie es hier steht.

Ich habe jetzt die Query Händler.

Die Query Händler benutzen ein Read Replica der Datenbank.

Darüber kann ich das Lesen stärker skalieren.

Das ist so ein bisschen, glaube ich, der Vorteil, der hier im Wesentlichen entsteht.

Diese beiden Teile sind z.B. unabhängig skalierbar.

Ich kann jetzt eben den Command Händler und den Query Händler in zwei unterschiedlichen Microservices ausführen.

Das führt eben auch dazu, dass ich bei diesem Pattern nicht so sicher bin, wie oft ich es eigentlich benutzen wollen würde.

Es macht nur dann Sinn, wenn ich sage, ich will diesen Query Händler Bereich unabhängig vom Schreiben skalieren.

Da, glaube ich, muss ich schon in signifikante Skalierungshöhen kommen, damit ich da Gewinn habe.

Ich kann auch etwas anderes machen.

Ich kann sagen, der Query Händler hat einen Snapshot und diesen Snapshot baue ich durch Event Sourcing Geschichten zusammen.

Das würde jetzt also bedeuten, dass ich hier diese Commands habe.

Hier wird irgendwas an Logik gebaut.

Ich sage z.B., dass die Bezahlung stattfindet und dann habe ich hier so ein Event Sourcing hin zu der Datenbank für den Snapshot.

Dort würde ich jedes Mal, wenn eine Berechnung erstellt wird, einen neuen Eintrag in diese Datenbank machen.

Das wird noch stärker dadurch entkoppelt.

Das könnte z.B. sinnvoll sein für so etwas wie Statistiken.

Für Statistiken will ich jetzt vielleicht wirklich ein getrenntes Lesemodell haben.

Da will ich auch andere Informationen haben, eher Summen.

Das ist etwas, wo ich dann vielleicht wirklich so ein CQS-Ding bauen will.

Da habe ich einmal die Daten, die Bewegungsdaten sind und dann habe ich die Daten eher für die Statistik.

Das würde jetzt wahrscheinlich auch bedeuten, dass Statistiken vielleicht sogar ein anderer context sind mit einem komplett anderen Datenmodell.

Vielleicht muss ich eben Statistiken anders modellieren.

Lesemodell habe, weil Rechnung kann ich eben nur einmal schreiben und erstellen und danach kann ich sie nur noch lesen.

Das ist eben tatsächlich ja fachlich korrekt.

Also ich kann nicht sagen, ups, ich habe mich bei dieser Rechnung vertan.

Lass mich sie mal kurz ändern, sondern ich muss dann sagen, ich habe mich bei der Rechnung vertan.

Lass mich die Rechnung stornieren.

Ich schreibe eine neue.

Das ist so ein Modell, was irgendwie sagt, okay, nicht Queries erlauben, sind halt getrennt, arbeiten auf dem Read Replica.

Und ich habe das andere Ding, was diese Rechnung erzeugt, das ist vielleicht gar nicht schlecht, um genau diese Policy halt auch strikt durchzusetzen.

Also vielleicht ist es aus so einer fachlichen Idee nicht so schlecht.

So Typhonix schreibt, das Wording in Snapshot ist eigentlich im Events-Hosting-Kontext was anderes, damit die Daten beim Aufbauen, eines Aggregates, dazwischen gespeichert werden.

Eigentlich sind doch eher Projektionen gemeint, oder?

Ja, kann gut sein, dass ich mich da sozusagen in der Benahmung vertan habe.

Guter Punkt, dank für den Hinweis.

Also genau, Snapshot bezieht sich vielleicht eben tatsächlich eher auf ein Aggregate, guter Punkt.

Ich habe zu dem Thema auch eine ganze Episode gemacht, da diskutiere ich das Thema nochmal tiefer, kann man sich irgendwie anschauen.

Und damit kommen wir jetzt zu diesem Thema, wo es beim letzten Mal schon Interesse gab.

Und ich bin etwas schneller sozusagen geplant, und das ist die Geschichte mit den Layern.

Da ist erst mal der Hinweis, das ist tatsächlich ein Pattern aus dem ursprünglichen blauen Domain-Domain-Design-Buch.

Also es gibt Layering als Pattern in diesem blauen Domain-Domain-Design-Buch.

Und damit ist eben gemeint, dass ich sage, ich habe hier die UI, die Logik und die Persistenz.

Und solche Schichten haben halt immer Abhängigkeiten von oben nach unten.

Das heißt, ich sage, die UI benutzt die Logik, die Logik benutzt die Persistenz.

Und das ist eben genau diese Geschichte mit der Schichtung.

Für mich ist das prototypische Modell für eine Schichtung eigentlich sowas wie dieser Netzwerk-Stack, wo ich eben unten ein physisches Layer habe, also Ethernet oder Wi-Fi.

Dann habe ich eben IP darüber, dann habe ich TCP darüber und dann irgendwelche Protokolle wie FTP oder HTTP.

Und das ist genau eben auch so eine Schichtung.

Also HTTP benutzt TCP oder UDP mittlerweile.

TCP und UDP benutzt IP, IP benutzt dann eben, was auch immer ich hier gerade verwende, Ethernet oder Wi-Fi.

Und es gibt eben nur die Abhängigkeit in dieser Richtung.

Ich erwähne das deswegen, weil diese Schichtung, glaube ich, aus einer technischen Perspektive oft Sinn macht.

Hier reden wir ja aber darüber, wie wir Fachlichkeit strukturieren.

Jetzt ist eben die Frage, also beim Netzwerk-Layering ist es beispielsweise so, dass dadurch, dass ich diese physische Schicht habe, kann ich jetzt relativ trivial von Ethernet auf Wi-Fi wechseln oder von Wi-Fi auf Mobilfunk.

Darüber ist das eben genau verborgen.

Das heißt also, ich kann diese untere Schicht genau austauschen und der Rest des Systems bleibt davon unberührt.

Hier würde ich argumentieren, ist das nicht so richtig ein guter Grund, weil die Persistenz werde ich wahrscheinlich seltener austauschen.

Also ist eben die Frage, warum wir dieses Pattern doch sind.

Und was Eric da eben gesagt hat, ist, ich separiere halt die Logik und habe die halt an genau einer Stelle.

Und dadurch baue ich das System so, dass es einfacher zu verstehen ist.

Und tatsächlich ist es so, dass Domain-Domain-Design jetzt gar nichts über die UI sagt.

Also was Domain-Domain-Design halt sagt, ist, okay, wir haben Services, wir haben Aggregates, solche Geschichten.

Dass wir eine UI haben, die das benutzt, ist irgendwie klar, aber wir sagen nicht, wie die strukturiert sein sollte.

Da gibt es ja auch Möglichkeiten, sowas halt irgendwie zu strukturieren.

Bei der Persistenz, naja, es gibt ein Repository als Pattern, aber das sagt auch noch nicht so wahnsinnig viel darüber aus, wie man das jetzt machen, genau umsetzen möchte.

Und da gibt es eben, also wir haben ja gerade eben zum Beispiel dieses Transaction Script gesehen, da gibt es halt durchaus auch andere Möglichkeiten.

Und das bedeutet, dass eigentlich der Kern dieses Patterns nur sagt, wir haben die Logik separiert.

Die Logik ist auch tatsächlich der Bereich, wo Domain-Domain-Design ganz viel darüber sagt.

Also Entity, Aggregates und so weiter, das sind alles Dinge, die in dieser Logik-Geschicht drin sind.

Und wir separieren das von der Persistenz, damit ich nicht, wenn ich über die Logik nachdenke und nachschaue, was da eigentlich gerade passiert, irgendwie genervt werde von SQL-Statements, die halt damit erstmal nichts zu tun haben oder JPA-Geschichten, die halt damit erstmal nichts zu tun haben, sondern das habe ich halt irgendwo anders.

Und analog ist es eben auch so, dass ich die UI davon getrennt habe.

Und auf diesem Abstraktions-Layer oder auf dieser Abstraktionsebene ist hexagonale Architektur eigentlich auch eine Lösung für dasselbe Problem.

Also hexagonaler, sprich Eric hätte halt in seinem Buch auch sagen können, wir sprechen hier nicht über Layer, sondern wir sprechen über hexagonal.

Dass er das nicht gemacht hat, hängt damit zusammen, dass eben Domain-Driven-Design, wenn ich mich nicht irre, etwas älter ist als hexagonale Architektur.

Also sprich, damals war irgendwie diese Dreischicht-Architektur mit UI-Logik und Persistenz das, was man typischerweise gemacht hat.

So, was ist jetzt bei hexagonaler Architektur anders?

Clean-Architecture ist da zum Beispiel auch sehr ähnlich.

Da sage ich halt, die Geschäftslogik exportiert irgendwelche Ports.

Also ich habe die Geschäftslogik, die hat jetzt hier zum Beispiel einen Port, um Notifications rauszuschicken.

Und dann habe ich Adapter, die diese Ports implementieren.

Also ich habe zum Beispiel einen E-Mail-Adapter.

Das heißt also, ich sage hier in der Business-Logik, ich habe eine Notification-Schnittstelle und der E-Mail-Adapter kann die jetzt implementieren.

Und hier sind zum Beispiel irgendwelche Business-Events, die ich jetzt an die UI rausgebe.

Hier ist irgendwie ein Admin-Ding, was ich an die Admin-UI rausgebe.

Und da habe ich einen Datenbank-Adapter, der diese Persistenz umsetzt.

So, was ich dadurch erreicht habe, ist, dass in allen Fällen die Abhängigkeit von außen, von dem Datenbank-Adapter beispielsweise zur Persistenz geht.

Der implementiert also das, was hier die Persistenz gerne haben möchte.

Oder hier von der Admin-UI hin zur Admin oder vom E-Mail-Adapter hin zur Notification.

Layering vs Hexagonale Architektur

Das ist bei Layering ja gerade nicht so.

Unterschiede

Bei Layering ist es eben so, dass die Logik die Persistenz benutzt.

Das heißt also, Logik hängt von Persistenz ab.

Hier hängt alles von der Logik ab.

Auch die Implementierung der Persistenz der Datenbank-Adapter.

Für mich ist das relativ ähnlich zu diesem Dependency-Immersion-Prinzip, wo ich also sage, wenn ich etwas benutze, dann kann ich auch die Schnittstelle hier definieren.

Dann ist die Abhängigkeit genau umgekehrt.

Also die Business-Logik verwendet den Datenbank-Adapter, ist aber softwaretechnisch nicht davon abhängig, sondern gibt ihm die Schnittstelle vor, die der Datenbank-Adapter implementieren muss.

Das heißt also, Datenbank-Adapter implementiert Persistenz-Port.

Dadurch ist die Abhängigkeit von draußen von diesen Adaptern hin zu den Ports.

Erstmal erreiche ich damit dasselbe Ziel wie die Slayering.

Was Eric ja sagt, ist, ich möchte gerne die Logik separieren von einem anderen.

Das erreiche ich hiermit auch, vielleicht sogar noch ein Tick besser, weil ich eben nicht diese Abhängigkeit zur Persistenz habe.

Ich habe außerdem den Vorteil, dass ich das Zeug besser testen kann, weil der Geschäfts-Logik-Kern von jeder Art von Technologie frei ist.

Wenn ich die Schichtung habe, dann ist es in der Logik so, dass ich tatsächlich von der Persistenz abhänge.

Vielleicht muss ich an welche Exceptions fangen, die die Persistenz wirft.

Hier ist es so, dass der Datenbank-Adapter von der Business-Logik abhängig ist.

Der definiert, was zu erwarten ist und wie die Schnittstelle aussehen soll.

Dann wird der Datenbank-Adapter das implementieren.

Die Business-Logik weiß nichts davon, was hier genau passiert.

Das führt zu einer besseren Testbarkeit, weil ich kann den Datenbank-Adapter und alle anderen Adaptoren durch etwas im Test ersetzen.

Ich kann sagen, da gibt es ein Ding, das sich so verhält wie eine Datenbank, oder ich kann etwas haben, was die Notifications abfragt, die sonst als E-Mails rausgehen.

Ich kann mit dem Geschäfts-Logik-Kern getrennt in Isolation testen.

Das ist auch ein signifikanter Vorteil.

Ich habe eine Episode mit Warren Vernon gemacht.

Er hat das Buch geschrieben, Domain-Domain-Design-Kompakt.

Eigentlich hatte ich erwartet, wir reden nicht so viel über Domain-Domain-Design, aber er hat viel gesprochen über Ports and Adapters bzw. hexagonale Architektur.

Eine Sache, die dabei total spannend war, war, dass Warren gesagt hat, ist das jetzt wirklich so viel komplizierter, eine hexagonale Architektur zu bauen.

Das ist tatsächlich ein guter Punkt.

Layering sagt, ich habe diese drei Sachen separiert und hexagonale Architektur sagt, ich habe bei Layering isoliert UI, Logik und Persistenz.

Hexagonale Architektur sagt jetzt, ich habe diese drei Sachen isoliert und die Persistenz hängt von der Logik ab.

Wenn ich hier bin bei Layering, da wäre es so, dass die Logik die Persistenz verwendet und davon abhängig ist.

Hier ist es genau umgekehrt.

Die Logik kennt die Persistenz nicht, sondern umgekehrt der Datenbankadapter implementiert diese Schnittstelle, die der Logik vorgibt.

In Bezug auf die UI ist es sowieso identisch, weil da ist es eben so, dass die UI die Logik verwendet.

Hier von oben nach unten gehen die Abhängigkeiten.

Das ist hier genauso.

Das heißt, die UI kommt jetzt irgendwie und benutzt hier diesen Port.

Das heißt, da ist der Abhängigkeitsfall genauso.

Das Einzige, was sich tatsächlich ändert, ist dieser andere Abhängigkeitsfall bei dem Datenbankadapter.

Relativ dumm gesagt, das kann ich erreichen, indem ich in der Geschäftslogik diese Schnittstellen definiere.

Ja, ich muss darauf aufpassen, dass es technologieunabhängig ist, aber das ist es dann eben auch.

Das heißt, eigentlich ist ein Punkt, wenn ich das separiere, ist der große Aufwand nicht mehr so ein großer Unterschied in Bezug auf den Aufwand zwischen hexagonaler Architektur und Layering.

Das fand ich einen ganz spannenden Gedanken, weil ja eben hexagonale Architektur so ein bisschen vielleicht den Ruf hat, kompliziert zu sein.

Und da ist, glaube ich, genau dieser Input interessant.

Und wie gesagt, die Alternative dazu wäre dann eben sowas wie ein Transaction Script, wo ich eben diese Schichtung nicht mache, wo ich also eben die Geschäftslogik nicht mehr so stark isoliere, einfach weil es halt zu wenig davon gibt oder gar keine.

Und da sollte ich vielleicht noch eine Sache hinzufügen.

Wenn ich dazu gezwungen bin, als Entwickler ein System zu bauen, das keine Geschäftslogik hat, bedeutet das, dass ich auch nicht wahnsinnig viel Wert generiere, würde ich behaupten.

Und es kann sein, dass ich tatsächlich ein System dann baue, was nicht wirklich das Geschäftslogikproblem löst.

Also die Kundin kommt und sagt, ich möchte gerne, dass ich folgende Daten sehe.

Okay, ist da Geschäftslogik?

Nein, ich will nur die Daten anzeigen.

Aber das ist wahrscheinlich nicht das, was diese Person wirklich will.

Was sie in Wirklichkeit will, ist, sie möchte jetzt entscheiden, wenn ich mir den Kunden angucke, kann ich entscheiden, ob das ein guter oder ein schlechter Kunde ist.

Dann ist es aber eigentlich so, dass es eine Geschäftslogik hinter steckt, die ich jetzt versuchen kann zu implementieren.

Also ich kann jetzt sagen, das ist nach unserer Policy ein guter Kunde, weil der hat viele Sachen bestellt und wenig zurückgegeben.

Das bedeutet, wenn ich ein System baue, was nur Daten von A nach B transportiert oder nur Daten anzeigt, ist das möglicherweise eine vertane Chance, ein System mit mehr Geschäftslogik zu bauen.

Das heißt, das, was wir hier diskutieren, dieses Thema mit dem Transaction Script, ist die richtige Lösung, wenn ich wenig Logik habe.

Aber Vorsicht, vielleicht ist es halt nur so, dass die Logik vor mir versteckt worden ist.

Bedeutet in der Zusammenfassung, ich habe das hier nochmal versucht aufzumalen und die ganzen Sachen in so einen Überblick zu bringen, von links nach rechts kommen immer mehr Details.

Das war etwas, was ich auch beim letzten Mal gesagt habe.

Also die Idee ist hier nicht, dass das ein Wasserfall ist, wo ich einmal durchlaufe, sondern ich arbeite auf unterschiedlichen Detail-Ebenen.

Das Grobgranularste, was ich habe, ist Big Picture Event Storming.

Da kriege ich einen Überblick über das Gesamtsystem, über die Events, die da laufen.

Ein nächster Detailierungsschritt wäre zu sagen, was ist eigentlich meine Core Domain?

Was ist das, worauf es ankommt?

Ich kann Strategic Design benutzen, als Alternative zu Team Topologies.

Da sage ich, welche Teams verantwortlich sind und wer was wo macht.

Ich weiß gar nicht, ob das detaillierter ist.

So geht es bei dem Big Picture Event Storming.

Bei so etwas wie Strategic Design oder Team Topologies oder Core Domain, sage ich, wie ich das aufteilen möchte auf Ebene von Teams.

Die arbeiten an einem oder mehreren Bauenden Kontexten.

Dann kommen diese ganz konkreten Techniken, über die wir gesprochen haben, also Tactical Design, um einen Bauenden Kontext zu implementieren und zu sagen, wie der umgesetzt wird.

Design Level Event Storming, um zu verstehen, was auf dieser Ebene die Anforderungen sind.

Ich kann so etwas benutzen wie Event Sourcing oder CQRS, wenn ich die dafür notwendigen Voraussetzungen habe.

In dem Sinne, dass ich einen Vorteil davon generiere, wenn ich im Fall von Event Sourcing die Events speichere, beziehungsweise im Fall von CQRS, wenn ich die Schreib- und die Lese-Teile voneinander trennen kann.

Dann gibt es Layers oder Hexagonale Architektur.

Da habe ich auch ein Fragezeichen hintergesetzt.

Bei Event Sourcing und CQRS würde ich behaupten, das benutze ich dann, wenn es passt.

Bei Layers oder Hexagonal würde ich sagen, eines von den beiden Patterns sollte ich anwenden, weil ich sonst am Ende die Logik und die anderen Sachen so sehr vermischt habe.

Jute Root schreibt noch, zusätzlich besteht Warn auf die Benennung Ports und Adapters anstatt Hexagonale Architektur.

Guter Punkt, ich erinnere das gar nicht.

Tatsächlich ist das vielleicht auch ein Punkt, der hier ein bisschen unglücklich ist.

Hexagonale Architektur heißt hexagonal, weil der Business-Logik-Kern und das Drumherum zufällig als Hexagon gemalt werden.

Es ist tatsächlich so, dass Ports und Adapter die bessere Bezeichnung sind.

Das drückt besser aus, worum es eigentlich geht.

Nämlich darum, diese Ports zu exponieren vom Geschäfts-Logik-Kern und dann Adapteuren zu haben, die das entsprechend umsetzen.

Dann haben wir es tatsächlich soweit.

Ein Hinweis, nächste Woche gibt es dieses Training-Rund um das Thema Domain-Design-Saliert-Legacy.

Das ist am 16.12. nachmittags.

Da könnt ihr gerne dazukommen.

Es ist preislich moderat und eine gute Möglichkeit, darüber zu reden, wie man Legacy mit DDD aufbaut.

Nächste Woche wird der Ralf mit Lars Röwekamp sprechen über das Thema Generative AI meets Software Architecture.

Das wird um 15.00 Uhr sein.

Freitag um 15.00 Uhr.

Das ist später als sonst üblich.

Vielleicht wollt ihr da wieder einschalten.

Vielen Dank für die Aufmerksamkeit.

Vielen Dank auch für die Fragen und Kommentare.

Ich wünsche euch allseits ein angenehmes und schönes Wochenende.