Laden...

Application Layer / Domain Events bei DDD

Erstellt von SuperVisor vor 8 Jahren Letzter Beitrag vor 8 Jahren 3.180 Views
S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 8 Jahren
Application Layer / Domain Events bei DDD

Hi zusammen

Ich setzte mich gerade mit dem Ansatz von Domain Driven Design auseinander. Dafür habe ich eine kleine Testapplikation mit einem Domain-Layer (Model), einem Infrastruktur-Layer (DB-Zugriff, MailSender) und einen Application-Layer erstellt.

Für was der Domain-Layer und der Infrastruktur-Layer zuständig sind, ist mir klar. Ich hab nur etwas Mühe mit dem Application-Layer. In Büchern und Blogs habe ich gelesen, dass dieser Layer möglichst "Leicht"/"Dünn" sein sollte und der Code meist prozedural gestaltet gestaltet wird. So wie ich das verstanden habe, sollten in einer Methode verschiedene Service, Domain-Methoden nacheinander orchestriert werden. Mir ist jedoch nicht ganz klar, wo ich die Grenze zwischen Funktionalität im Model und dem Application-Layer ziehen soll/kann.

Ein Beispiel: Nehmen wir an, ich muss eine Rechnung erstellen. Nachdem die Rechnung erstellt wurde soll eine Email an den Empfänger gesendet werden und je nach Rechnungstyp ein Dokument erstellt werden. Und hier stellt sich nun die Frage wo ich welche Funktionalität implementieren soll. Ich sehe folgende Möglichkeiten:

**- Ich löse das im Application-Layer. **Im Application-Layer habe ich einen Service mit der Methode "RechnungErstellen". Dieser Methode muss die ID der Rechnung übergeben werden. Die Methodenlogik lädt sich nun die Rechnung aus der Datenbank und ruft die Methode "Verrechnen" auf dem Domain-Objekt auf. Die "Verrechnen"-Methode ändert einige Eigenschaften wie z.B. der Status der Rechnung (Offen --> Verrechnet). Im Anschluss wird in der "RechnungErstellen"-Methode der Empfänger der Rechnung ermittelt und eine Email an diesen gesendet. Muss aufgrund des Rechnungstyps ein Rechnungsdokument erstellt werden, wird dies mit einem weiteren Methodenaufruf implementiert.

**- Ich löse das im Domain-Layer. **Auf einem geladenen Rechnungs-Objekt wird die Methode "Verrechnen" aufgerufen. Dieser Aufruf ändert den "Status" und erstellt zusätzlich einen Domain-Event welcher an einen Handler gesendet wird. Dieser Handler versendet dann die Email. Ein weiterer Domain-Event könnte dann noch ein Dokument erstellen sofern dies erforderlich ist.

So, wo implementier ich welche Logik nun am besten? Aus meiner Sicht sollte beide Varianten möglich sein, jedoch finde ich es nicht sehr optimal einen Application-Layer zu erstellen nur um Methoden seriell aufzurufen, wenn ich das auch mit Domain-Events lösen kann. Ich sehe im Application-Layer höchstens den Vorteil die verschiedenen Methodenaufrufe mit einer Transaktion zu schützen.

Besten Dank für eure Denkanstösse.

Gruss
SuperVisor

16.807 Beiträge seit 2008
vor 8 Jahren

Ich sehe in Variante 2 eine Verletzung von einigen Prinzipien und Verantwortlichkeiten.

In Dein Domain Layer sollte überhaupt keine Logik rein.
Auch sollte Dein Rechnungs-Objekte in meinem Verständnis nicht unbedingt die Methode "Verrechnen" haben. Das würde jedenfalls den POCO Ansatz durchkreuzen.
POCOs dürfen zwar Methoden haben, aber keine Methoden mit einem Business-Logik-Hintergrund.

In Variante 2 würde der Service eher "RechnungenService" heissen und die Methode "Rechnungerstellen"; klassisches CRUD.
Das wäre aber ein Beispiel, an dem eine Workflow-Engine (da wir es neulich hier davon hatte), u.a. mit CQRS auch wirklich sinn machen würde.
Dann hast Du einen Commands,die RechnungErstellen, RechnungVerrechnen, RechnungEmailVersenden heissen.

74 Beiträge seit 2014
vor 8 Jahren

In Dein Domain Layer sollte überhaupt keine Logik rein.

Dazu hat man doch überhaupt erst den Domain Layer, zumindest wenn es nach DDD geht. Ansonsten bekomm man am Ende ein Anemic Domain Model. POCOs kann man trotzdem noch nutzen, eben um mit der DB zu kommunizieren.

16.807 Beiträge seit 2008
vor 8 Jahren

Jain. Wenn Du nach dem strikten Prinzip von Enterprise Design gehst, dann repräsentiert der Domain layer quasi die Businesslogik.
Macht man es nun etwas granularer bzw. schaut in die meisten (wobei, vor allem im Web-Umfeld) realen Umsetzungen, dann gibt es Domain Services (manche nennen sie auch Object Services) aber auch Application Services .
Domain Services sind eben für die eine Domain zuständig, Application Services aber zB. für die Kommunikation von mehrere Domain Services.

Du hast dann den Presentation Layer, Application Layer, Domain Layer und den Data Layer.

Domain Services liegen dann meistens im Layer der Domains, Application Services aber im Business Layer.

POCOs selbst sollten gar nicht kommunizeren, sondern mehr oder weniger nur für den Austausch dienen.

742 Beiträge seit 2005
vor 8 Jahren

Ich sehe dass wie Lando.

Der Domain Layer bildet die verschiedenen Bereiche deiner Anwendung ab, siehe Bounded Context (http://martinfowler.com/bliki/BoundedContext.html) und sollte inbesondere die Domain Logik enthalten:

  1. Definition von Domain Objekten.
  2. Definition von deren Eigenschaften
  3. Definition von deren Methoden (Business Operationen)

Das sind auch diejenigen Begriffe, die man mit Domainexperten kommunizieren wird. "Wenn ein Käufer die Bezahlung durchgeführt hat, dann wird eine Bestellung abgeschlossen und eine Nachricht an das Lagersystem gesendet."

also "order.Pay(...) => OrderPayedMessage". Externe System bleiben aber außerhalb (eigenes Projekt, Solution) und werden über Gateways angeschlossen (Datenbank, Email System, Reporting System usw.) https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

Außerhalb dessen steht der Application Layer, der die Use Cases abbildet. Auch über Bounded Context Grenzen hinweg. Hier bieten sich Technologien wie Messaging, verteilte Transaktionen, Sagas usw. an.

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 8 Jahren

Wenn die die Antworten von Lando und malignate lese, denke ich, dass ich auf dem richtigen Weg bin.

Macht man es nun etwas granularer bzw. schaut in die meisten (wobei, vor allem im Web-Umfeld) realen Umsetzungen, dann gibt es Domain Services (manche nennen sie auch Object Services) aber auch Application Services .
Domain Services sind eben für die eine Domain zuständig, Application Services aber zB. für die Kommunikation von mehrere Domain Services.

Ohne dass ich jemanden angreifen oder beleidigen möchte, aber ich denke, dass viele das Konzept von DDD nicht oder nicht ganz verstehen und die Begriffe verwenden, wie sie so aktuell so gebräuchlich sind. Ein Domain Services bietet aus meiner Sicht z.B. Funktionalität an, welche in einem Domain Model nicht direkt zur Verfügung gestellt werden kann. Das könnte z.B. ein Service sein, um die Versandgebühren für ein Paket aufgrund des Gewichtes zu berechnen sein.

Außerhalb dessen steht der Application Layer, der die Use Cases abbildet. Auch über Bounded Context Grenzen hinweg.

Und genau hier stellt sich nun meine Frage. Nehmen wir das Beispiel von malignate. "Wenn ein Käufer die Bezahlung durchgeführt hat, dann wird eine Bestellung abgeschlossen und eine Nachricht an das Lagersystem gesendet."

Ich kann diese Funktionalität aus meiner Sicht komplett im Domain Object abhandeln. Ich würde das mit Domain Events lösen. Im Domain Event "OrderPayedMessage" würde dann eine Nachricht an das Lagesystem gesendet. Aber genau so gut, könnte ich diese Funktionalität im Application Layer implementieren. Methode "PayOrder" --> Bestellung laden, Status setzen, Bestellung in DB speichern, Nachricht versenden.

Wo ziehe ich da nun die Grenze wo was hingehört? Gibt es Entscheidungshilfen ob ich die Funktionalität im Domain Layer (mit Events) oder im Application Layer implementieren soll?

16.807 Beiträge seit 2008
vor 8 Jahren

Ich denke nicht, dass es ein Verständnisproblem ist - viel eher ein Theorie-Praxis-Thema.
DDD ist nicht der Weisheits letzter Schluss, wie alles andere auch. Dahingehend gibt es viele Varianten bzw. Abwandlungen.
Martin Fowler ist da auch nicht der einzige, der sich dazu Gedanken gemacht hat - auch wenn er oft zitiert wird.

Wenn Dein Service nur auf dem Gewicht basiert und Du die dahingehenden 1:1 Informationen hast, dann sehe ich das auch so, dass es ein Domainservice ist.
Meist brauchst Du bei einem Versandservice aber weitere Informationen, wie zB. das Land oder willst Live-Informationen. Da brauchst Du sehr schnell die Informationen eines (andere) Application oder Infrastructure Service.
Wo befindest Du Dich dann? 😉

Die Grenze ist relativ einfach:

  • Domain Services ist die Business Logik
  • Application Services stehen externen Systemen zur Verfügung, zB eine WebAPI. Sie werden aber auch oft für die interne Kommunikation parallel genutzt (Theorie vs. Praxis)
  • Infrastructure Service wäre zum Beispiel Deine Mail-Exchange-Kommunikation

Und da gibts auch einfache "Regeln":

  • Domainservices agieren nur mit Domain-Entites und Gateways, nie mit Infrastructure Services. Umgekehrt kennen Infrastructure Services keinerlei Domain Informationen.
  • Nur Application Services dürfen mit Infrastructure Services kommunizieren
  • Mit Gateways dürfen bzw. können alle Services kommunizieren

Spätestens die Sache mit der E-Mail bleibt für mich eine Verletzung - da kann man mir (indirekt und unterschwellig) nun eine fehlende Kompetenz bei DDD unterstellen wie man will.

Es gibt auch irgendwelche 3 oder 5 Fragen, die Du einem Service stellen kannst, und es wird immer einer dieser drei Services als Antwort kommen - und nur einer.
Aber diese Fragen kann ich nicht mehr auswendig 😃
Bei Infrastructure gibts nur eine: ist dieser Teil meiner Logik oder läuft meine Anwendung auch ohne diesen?

S
SuperVisor Themenstarter:in
44 Beiträge seit 2012
vor 8 Jahren

Ich denke nicht, dass es ein Verständnisproblem ist - viel eher ein Theorie-Praxis-Thema. DDD ist nicht der Weisheits letzter Schluss, wie alles andere auch. Dahingehend gibt es viele Varianten bzw. Abwandlungen.
Martin Fowler ist da auch nicht der einzige, der sich dazu Gedanken gemacht hat - auch wenn er oft zitiert wird.

So kann man das natürlich auch sehen! 👍

Meist brauchst Du bei einem Versandservice aber weitere Informationen, wie zB. das Land oder willst Live-Informationen. Da brauchst Du sehr schnell die Informationen eines (andere) Application oder Infrastructure Service.
Wo befindest Du Dich dann? 😉

Gute Frage, ganz spontan hätte ich jetzt gesagt, ich würde über ein Interface auf die Implementierung im Infrastruktur Layer zugreifen. Das könnte dann ein Webservice sein, oder ein DB Aufruf. Aber da würde ich schon gegen die erste Regel verstossen. Wo kommt so etwas aus deiner Sicht/Erfahrung hin?

Was verstehst du unter Gateways?

pätestens die Sache mit der E-Mail bleibt für mich eine Verletzung - da kann man mir (indirekt und unterschwellig) nun eine fehlende Kompetenz bei DDD unterstellen wie man will.

Ich nehme an, du würdest eine Mail im Application Layer versenden? Weshalb?

16.807 Beiträge seit 2008
vor 8 Jahren

Die Mail gehört in den Infrastructur Layer; der Application Layer deckt den Workflow/UseCase ab.
Aber wie bereits gesagt: der Domain Servce soll/darf aber nicht auf den Infrastructure Layer zugreifen.
Gateway ist zB die Schnittstelle zur Datenbank.

742 Beiträge seit 2005
vor 8 Jahren

Im Domain Event "OrderPayedMessage" würde dann eine Nachricht an das Lagesystem gesendet

Nein, ein Event macht nichts. Ein Event ist einfach mal eine Benachrichtigung, dass etwas passiert ist (Vergangenheit!). Diese könnte das Business Objekt in ein EventBus schreiben. Wichtig IEventBus, ein Interface. Ob es dann NServiceBus ist oder InMemory Implementierung spielt keine Rolle.

Um das mal praktisch zu gestalten. Ich gehe ungefähr so vor:

  1. Ich beschreibe meine Domaine. Das sind Business Objekte und Services (welche komplexe Business Logik nochmal austauschbar wegkapseln) in einer Assembly und halte hier alle Abhängigkeiten zu externen Implementierungen weg. Kein Referenz auf Entity Framework oder NLog usw. Wenn man z.B. doch mal noch was Loggen muss, gibt es schöne Packete wie Common Logging. Außerdem beschreibe ich in diesem Projekt/Assembly über Interfaces die externen Schnittstellen. Das können Datenbanken sein (Repositories) oder ein Mail Service oder was auch immer.

  2. Eine Schicht weiter außen steht die Implementierung der externen Services an, also z.B. MongoDBUserRepository oder XYZMailService oder InterfaxFaxService usw. Das sind meist sehr viele Assemblies oder Solutions.

  3. Wieder eine Schicht draußen ist mein Application Layer. Hier aggregiere ich meine Domain Objekte und Business Events zu komplexeren UseCases. Die Art der Aggregation kommt auf die Domaine an. Man kann viel über Events machen oder auch direkte Methodenaufrufe usw. Hier implementiere ich z.B. auch meine API und mache die Transformation von DTO's für die WebAPI zu Business Objekten usw.

  4. Die Host Anwendung packt dann alles zusammen und definiert sowas wie:

  • IIS oder SelfHost?
  • JSON oder Protobuf?
  • MongoDB oder Entity Framework
    usw.

Wichtig: Assembies von Schicht 2 und Schicht 3 können leicht wiederverwendet werden, z.B. wenn man Anwendung in mehrere Dienste aufgeteilt wird (z.B. WebAPI Prozesse + Worker Prozesse).

Sowas wie ein Infrastructure Layer gibt es meiner Meinung nach gar nicht (Auch noch nie davon gelesen). Das wären eher alle externen Systeme oder deren Anbindungen Gateways. Ganz wichtig ist hierbei, dass du eben nie direkt, sondern immer über Interfaces drauf zugreifst. Das hat v.a. drei Gründe:

  1. Du definierst sauber die Anforderungen an Intfrastructure Komponenten
  2. Du kannst diese austauschen
  3. Es ist einfacher zu testen

Dein Mail System ist Teil verschiedener Layer (Bereiche)

  1. Infrastructure: Du wirst ein Interface für MailServices definieren und ein Gateway dazu schreiben

  2. Domain Layer: Wahrscheinlich wirst du Domain Logik haben um die Email zu formulieren, sowas wie ein IUserRegisteredMailCompositionService (scheiß Wort). Je nach Anforderungen könnte das Business Objekt auch diesen Service aufrufen, da reicht schon ein "mailService.SendUserRegistered(this);"

  3. Application Layer: Wenn du die Orchestrierung über Domain Events machst, würde ich im Application Layer das Event abbonnieren und dann den IUserRegisteredMailCompositionService aufrufen.

16.807 Beiträge seit 2008
vor 8 Jahren

Allein wenn man nur Infrastructure Service in Google eingibt bekommt man tausende Treffer... und man sieht auch direkt viele viele schöne Treffer zur Relevanz zu DDD.
Wenn man nun die Existenz von Infrastructure Layer in DDD diskutieren müssen, dann sollten wir evtl nochmal weiter vorn anfangen 😉

Die Dinger haben schon ihren Sinn und sollten in keinem Falle mit Gateways verwechselt werden - aber auch hier sieht man das offensichtlich verschieden.

Gateways dürfen von Domains aufgerufen werden, Infrastructure Services nicht.
Ich persönlich finde es ein ganz arg schlimmes Design, das auch früher oder später mal unter Garantie aufgrund der Kopplung Probleme machen wird, wenn dem doch so ist.
Aber man lernt draus 😉

742 Beiträge seit 2005
vor 8 Jahren

Ich habe leider keine Ahnung, was du mit "Infrastructure Layer" meinst. Ich habe natürlich davor Gegoogelt und habe nicht wirklich eine gute Quelle dazu gefunden. Nicht jede Sammlung von ähnlichen Komponenten/Klassen hat den Begriff "Layer" verdient.

Ich denke jeder Infrastructure Service ist auch ein Gateway (Gateway = An object that encapsulates access to an external system or resource.) Umgekehrt gilt das natürlich nicht. Da beispielsweise auch ein Repository ein Gateway ist. Man würde das aber wahrscheinlich eher als Domain Service sehen. Man sieht hier, dass die Begriffe nicht klar voneinander getrennt sind.

Ob man jetzt Infrastructure Services aus dem Domain Layer hängt von den Anforderungen ab.
Die Frage ist also beispielsweise: Muss unter jeden Umständen, beim Anlegen eines Benutzers eine Email verschickt werden? Wenn die Antwort Ja ist (also auch beim Batch Insert von Nutzern, z.B. wenn der Benutzer Daten migriert) dann ist das eine Domain Service und wird vom Domain Model aufgerufen (um konsistent zu sein). Wahrscheinlich würde man noch einen Domain Service (IUserRegisteredMailCompositionService) dazwischenschalten. Ein gutes Beispiel für Infrastructure Services sind übrigens Repositories.

Ich persönlich finde es ein ganz arg schlimmes Design, das auch früher oder später mal unter Garantie aufgrund der Kopplung Probleme machen wird, wenn dem doch so ist.

Was ist "es"?

16.807 Beiträge seit 2008
vor 8 Jahren

Ich meine natürlich Infrastructure Services, nicht Infrastructure Layer - sorry.
Daher korrigiere ich:

Wenn man nun die Existenz von Infrastructure Services in DDD diskutieren müssen, dann sollten wir evtl nochmal weiter vorn anfangen 😉

Manche bezeichnen das wirklich als Layer zusammen mit Hosting und Co - auch ein Projekt an dem ich beteilit bin; diese Meinung / Ansicht teil ich aber nicht.

Infrastructure Services haben keinerlei Kenntnis von Domains, denn sie sollen vollständig modular sein. Sie sollten so konzipiert sein, dass sie keine direkte Auswirkung auf die Geschäftslogik haben.

Gateways hingegen kennen Domains und interagieren zB. zwischen Domains und Datenbanken in Form von Repositories.

Das unterscheidet sie.
Und ja, ich halte es für eine sehr schlechte Implementierung, wenn man das mischt, wie Du es gerade durch die Gateway-Verallgemeinerung machst 😃

742 Beiträge seit 2005
vor 8 Jahren

Ich glaube wir haben hier ein Begriffsproblem. Man müsste erstmal alle Begriffe durchdefinieren, damit man richtig diskutieren kann. Da zumindest die Internetquellen hier auch nicht ganz konsequent sind und Patterns auch Doppeldeutungen haben können wird es sonst schwierig.

Ich kenne jetzt immer noch nicht genau deine Definition von Gateway und Infrastructure Services und man muss auch bei der Richtung der Abhängigkeiten (wer kennt wen) sehr vorsichtig sein.

Die Gefahr ist daher sehr groß, dass wir hier einfach einander vorbeireden. Deshalb sollte man den Thread glaube ich einfach schließen, zumal es dem Thread Ersteller glaube ich nicht mehr weiterhilft.