Dieses Dokument beschreibt foodtruacker, ein Projekt, das Domain-Driven Design (DDD), CQRS und Event Sourcing implementiert. Es nutzt ASP.NET Core und konzentriert sich auf die Verbesserung der Wartbarkeit in komplexen Geschäftsdomänen. Das Projekt verwendet zur Veranschaulichung einen vereinfachten fiktiven Geschäftsfall. Diese ausführliche Erläuterung behandelt die Beweggründe, Funktionen, Implementierungsdetails und relevanten Technologien.
foodtruacker – Implementierung von DDD, CQRS und Event Sourcing
Dieses ereignisgesteuerte Projekt nutzt Prinzipien, Frameworks und Architekturen – alle mit dem Gedanken, die Wartbarkeit beim Umgang mit Systemen zu verbessern, die komplexe Geschäftsdomänen widerspiegeln. Die Web-API der Anwendung basiert auf dem ASP.NET Core-Framework von Microsoft und implementiert domänengesteuertes Design sowie die CQRS- und Event-Sourcing-Muster. Ein fiktiver Business Case bildet den Grundstein für dieses Projekt und ist das Ergebnis eines Event-Storming-Workshops.
Bitte beachten Sie: Die in diesem Projekt vorgestellte fiktive Geschäftsdomäne ist stark vereinfacht und sollte nur als Anbieter zuordenbarer Anwendungsfälle betrachtet werden.
Motivation
Da es nicht immer am besten ist, CRUD-Operationen und POCO-Objekte in Projekten mit recht komplexen Geschäftsdomänen zu verwenden, habe ich beschlossen, dieses Projekt als praktische Umsetzung meiner Forschung und meines Interesses an Domain-Driven Design (DDD) zu erstellen. Ansatz zur Entwicklung von Software.
Da der in diesem Projekt vorgestellte fiktive Geschäftsszenario stark ereignisgesteuert ist, habe ich beschlossen, auch die Muster CQRS und Event Sourcing zu implementieren. Beide sind mir bei der Recherche für dieses Projekt aufgefallen und passen gut zu DDD.
Merkmale
Überblick
Dieses Projekt besteht aus einer ausführbaren Web-API-Anwendung und mehreren Funktionskomponenten, die jeweils über Klassenbibliotheken bereitgestellt werden. Der Code ist nach Namespaces organisiert. In in Visual Studio erstellten ASP.NET Core-Anwendungen werden die Namespaces standardmäßig automatisch aus der Ordnerstruktur der Projekte erstellt. Im folgenden Diagramm finden Sie einen umfassenden Überblick über die Ordner- (und Namespace-)Struktur dieses Projekts:
Einführung
Event Storming
Ein flexibles Workshop-Format zur gemeinsamen Erkundung komplexer Geschäftsfelder, erfunden von Alberto Brandolini. Es handelt sich um eine äußerst einfache Methode zur schnellen Verbesserung, Planung, Untersuchung und Gestaltung von Geschäftsabläufen und -prozessen in Ihrem Unternehmen.
Der Workshop besteht aus einer Gruppe von Personen mit unterschiedlichem Fachwissen, die mithilfe farbiger Haftnotizen gemeinsam relevante Geschäftsprozesse gestalten. Für einen EventStorming-Workshop ist es zwingend erforderlich, dass sowohl die richtigen Personen anwesend sind als auch genügend Fläche zum Anbringen der Haftnotizen vorhanden ist. Zu den erforderlichen Personen gehören in der Regel diejenigen, die die zu stellenden Fragen kennen (typischerweise Entwickler) und diejenigen, die die Antworten kennen (Domänenexperten, Produktbesitzer).
Ziel dieses Workshops ist es, dass die Teilnehmer voneinander lernen, Missverständnisse aufdecken und widerlegen und, z. B. in diesem GitHub-Projekt, den Grundstein für die Entwicklung einer ereignisbasierten Softwarelösung legen, die eine entsprechende Geschäftsdomäne widerspiegelt.
Domain-driven Design (DDD)
Ein Ansatz zur Softwareentwicklung, bei dem die Entwicklung auf der Programmierung eines Domänenmodells basiert, das über ein umfassendes Verständnis der Prozesse und Regeln einer entsprechenden Geschäftsdomäne verfügt. Der Begriff „Domain-driven Design“ wurde von Eric Evans in seinem gleichnamigen Buch geprägt.
DDD zielt darauf ab, die Erstellung komplexer Anwendungen zu erleichtern und konzentriert sich auf drei Grundprinzipien:
Das Buch von Eric Evans definiert einige gängige Begriffe für Domain-Driven Design:
Domänenmodell
Ein Abstraktionssystem, das die Prozesse und Richtlinien einer Geschäftsdomäne beschreibt und zur Abwicklung erforderlicher Aufgaben im Zusammenhang mit dieser Domäne verwendet wird.
Allgegenwärtige Sprache
Wörter und Aussagen für bestimmte Elemente des Geschäftsbereichs. Um Missverständnissen vorzubeugen, sollten alle Teammitglieder bestimmte Begriffe übernehmen, typischerweise die, die von den Fachexperten verwendet werden.
Begrenzter Kontext
Eine konzeptionelle Grenze, innerhalb derer ein bestimmtes Domänenmodell definiert und anwendbar ist. Dies stellt typischerweise ein Subsystem oder einen Arbeitsbereich dar. Es handelt sich hauptsächlich um eine sprachliche Abgrenzung, wobei jeder begrenzte Kontext seine eigene allgegenwärtige Sprache hat.
Beispiel: Kundenverwaltung, bei der ein Benutzer „Kunde“ heißt.
Das Buch von Eric Evans differenziert bestimmte Teile des Domänenmodells weiter. Um nur einige zu nennen:
Juristische Person
Ein Objekt, das durch seine Identität und nicht durch seine Attribute definiert wird.
Beispiel: Eine Person bleibt immer dieselbe Person, unabhängig von der Wahl der Jacke, der Haarfarbe oder der Sprache, die zu einem bestimmten Zeitpunkt gesprochen wird.
Wertobjekt
Ein Objekt, das ausschließlich durch den Wert seiner Attribute definiert wird. Wertobjekte sind unveränderlich und haben keine eindeutige Identität. Wertobjekte können durch andere Wertobjekte mit denselben Attributen ersetzt werden.
Beispiel: Beim Fokussieren auf eine Person kann eine kaputte Sonnenbrille ganz einfach durch eine neue, gleich aussehende Sonnenbrille ersetzt werden.
Aggregat
Ein Cluster aus einer oder mehreren Entitäten und optionalen Wertobjekten, die zu einer einzigen Transaktionseinheit vereint sind. Eine Entität bildet die Basis des Aggregats und wird daher zum Aggregat-Wurzel erklärt. Auf alle Eigenschaften seiner zusammenarbeitenden Entitäten und Wertobjekte kann möglicherweise nur über diese einzelne Basisentität zugegriffen werden. Ein Aggregat muss immer in einem konsistenten Zustand sein. Bei der objektorientierten Programmierung erfolgt dies typischerweise durch die Verwendung privater Setter und geschützter Getter.
Beispiel: In einem Autoverkaufskontext wird ein Auto (eine Entität) durch seine Fahrzeugidentifikationsnummer definiert. Dieses Auto verfügt möglicherweise über vier Räder (Wertobjekte), die nach einer gewissen Zeit möglicherweise ersetzt werden müssen.
Domänenereignis
Ein Objekt, das als Ergebnis einer Aktivität innerhalb des Domänenmodells erstellt wird. Es dient der Speicherung und Weiterleitung von Informationen im Zusammenhang mit dieser Aktivität. Domänenereignisse werden in der Regel für Aktivitäten erstellt, die die Domänenexperten für relevant halten.
Sechseckige Architektur (Anschlüsse und Adapter)
Ein im Softwaredesign verwendetes Architekturmuster, das 2005 von Alistair Cockburn vorgeschlagen wurde. Das Muster zielt darauf ab, ein hohes Maß an Wartbarkeit zu erreichen und beschreibt eine Anwendung in drei Schichten. Jede Schicht kommuniziert mit der/den benachbarten Schicht(en) über Schnittstellen (Ports) und Implementierungen (Adapter):
Die wichtigste Regel in diesem Architekturmuster ist, dass Abhängigkeiten nur nach innen zeigen können. Nichts im inneren Kreis kann überhaupt etwas über etwas im äußeren Kreis wissen. Alle Abhängigkeiten, die nach außen zeigen möchten, z. B. der Aufruf einer Datenbank aus der Anwendungsschicht, müssen über Inversion of Control (IoC) oder Dependency Injection (DI) instanziiert werden.
CQRS mit MediatR (einem vorgefertigten Messaging-Framework)
CQRS steht für Command/Query Responsibility Segregation und wurde erstmals 2010 von Greg Young beschrieben. Es basiert auf dem Command Query Separation (CQS)-Prinzip und ermöglicht die Trennung von Lese- und Schreibvorgängen. CQS erklärt:
Die Verbesserung von CQRS gegenüber CQS besteht darin, dass diese Befehle und Abfragen als Modelle und nicht als Methoden behandelt werden. Diese Modelle können als Objekte an einem Punkt versendet werden, um dann von den erforderlichen Handlern an einem anderen Punkt im System verarbeitet zu werden, wobei jeder seine Antwortmodelle zurückgibt, um jede Aktion klar zu trennen.
Das Mediator-Muster ermöglicht die lose gekoppelte Implementierung von Befehlen/Abfragen und Handlern unter Verwendung eines Mediator-Objekts. Objekte kommunizieren nicht mehr direkt miteinander, sondern über den Vermittler.
Das MediatR-Framework ist eine Open-Source-Implementierung des von Jimmy Bogard erstellten Mediator-Musters. Es wird in diesem Projekt für die Kommunikation zwischen der Framework-Schicht und der Anwendungsschicht verwendet. Es wird auch zum Projizieren von Daten aus der Befehlsdatenbank in die Abfragedatenbank verwendet.
Event-Sourcing
Ein architektonisches Entwurfsmuster zum Speichern jeder Änderung im Status einer Anwendung, anstatt nur den aktuellen Status der Daten in einer Domäne zu speichern. Dieses Muster wurde von Greg Young eingeführt und seitdem mehrfach übernommen.
Das Muster soll jede Änderung am Status einer Anwendung als Ereignisobjekt erfassen. Diese Ereignisobjekte werden dann in der Reihenfolge ihres Auftretens nur angehängt gespeichert. Dies ermöglicht nicht nur die Wiederherstellung des aktuellen Zustands eines Objekts über die Abfolge der bisher stattgefundenen Ereignisse, sondern ermöglicht letztendlich auch eine Zeitreise in die Vergangenheit und die Wiederherstellung des Zustands des Objekts für einen bestimmten Zeitpunkt.
Ein Bankkonto kann ein gutes Beispiel für das Event-Sourcing-Prinzip sein. Jedes Mal, wenn Geld abgehoben oder eingezahlt wird, wird nicht nur der aktuelle Kontostand aktualisiert, sondern auch der Wechselbetrag. Der aktuelle Kontostand wird dann berechnet, indem die Abfolge der Ereignisse mit den entsprechenden Informationen darüber, wie viel Geld jedes Mal abgehoben oder eingezahlt wurde, durchgegangen wird.
Event Sourcing passt gut zu Domain-driven Design, da es sich hervorragend zum Speichern von Domain-Events eignet, die durch das Domain-Modell bei jeder Änderungsanforderung ausgelöst werden.
Auch Event Sourcing profitiert stark von CQRS. Anstatt eine Abfrage in der Event Sourcing-Datenbank durchführen zu müssen, die alle aufgezeichneten Ereignisse im Zusammenhang mit dem angeforderten Objekt durchgehen müsste, um den aktuellen Status wiederherzustellen, kann diese Abfrage in einer dedizierten Abfragedatenbank durchgeführt werden. Diese Abfragedatenbank wird von ihren eigenen Ereignishandlern aktualisiert und überwacht die gleichen Ereignisse, die unmittelbar nach dem Anhängen an die Ereignisbeschaffungsdatenbank gesendet werden. Diese Aktualisierungsvorgänge werden Projektionen genannt.
Diese Trennung der Datenbanken eröffnet auch ein großes Potenzial für Skalierbarkeit und Leistungsoptimierung. Mehrere Instanzen der Query-Datenbank können erstellt und synchron gehalten werden, indem einfach ihre Event-Handler auf die Ereignisse warten, die vom Event Sourcing Database Client gesendet werden, direkt nachdem eine relevante Änderung am Status einer Anwendung aufgetreten ist. Die Wahl des Datenbanktyps sowie der Grad der Datendenormalisierung, optimiert pro Abfrage, können die Leistung erheblich steigern.
Diese ständige Aktualisierung des Lesemodells kann entweder synchron oder asynchron erfolgen. Letzteres geht zu Lasten der letztendlichen Konsistenz, da das Lesemodell für eine winzige Zeitlücke (normalerweise Millisekunden) nicht mit dem Schreibmodell synchronisiert ist.
Geteilter Kernel
Eine gemeinsame Bibliothek für die Domänenschicht, die gemeinsame domänengesteuerte Design-spezifische Basisklassen, Domänenentitäten, Wertobjekte usw. enthält, die über Bounded Contexts hinweg gemeinsam genutzt werden.
Erste Schritte
Um dieses Projekt so zum Laufen zu bringen, wie es ist, können Sie die folgenden Schritte ausführen:
Voraussetzungen
Aufstellen
Starten Sie https://localhost:5001/swagger/index.html in Ihrem Browser, um die Swagger-Dokumentation Ihrer API anzuzeigen.
Verwenden Sie Swagger, Postman oder eine andere Anwendung, um eine POST-Anfrage an https://localhost:5001/api/Administration/Register zu senden, um Ihr erstes Administratorkonto zu registrieren. Senden Sie das folgende Objekt:
Sehen Sie sich die Konsolenanwendung oder die Ausgabe an, die für die Protokolle der Anwendung neu konfiguriert wurde. Nach jeder erfolgreichen Registrierung eines Benutzers sollte ein vom EmailService bereitgestellter E-Mail-Bestätigungslink in die Protokolle geschrieben werden. Kopieren Sie diese URL, fügen Sie sie in Ihren Browser ein und drücken Sie die Eingabetaste, um die Registrierung abzuschließen. Fühlen Sie sich frei, diese unsachgemäße Implementierung eines E-Mail-Dienstes zu ändern oder darauf aufzubauen ;-)
Sie sind bereit. Als nächstes anmelden.
Starten Sie http://localhost:2113/ in Ihrem Browser, um die EventStoreDB-GUI anzuzeigen. Öffnen Sie die Registerkarte „Stream-Browser“, um alle gespeicherten Ereignisse anzuzeigen.
Tests können ausgeführt werden, indem Folgendes ausgeführt wird:
Technologien
Dieses Projekt nutzt die folgenden Technologien/NuGet-Pakete:
Ressourcen / Empfohlene Lektüre
Alberto Brandolini:
https://www.eventstorming.com
Vaughn Vernon:
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_1.pdf
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_2.pdf
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_3.pdf
Alistair Cockburn:
https://web.archive.org/web/20180822100852/http://alistair.cockburn.us/Hexagonal+architecture
Robert C. Martin (Onkel Bob):
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
Cesar de la Torre, Bill Wagner, Mike Rousos:
https://docs.microsoft.com/en-us/dotnet/architecture/microservices/
Greg Young
https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
https://cqrs.wordpress.com/documents/building-event-storage/
https://msdn.microsoft.com/en-us/library/jj591559.aspx
Martin Fowler:
https://www.martinfowler.com/bliki/CQRS.html
Jimmy Bogard:
https://github.com/jbogard/MediatR
https://www.youtube.com/watch?v=SUiWfhAhgQw
Domänengesteuertes Design:
https://dddcommunity.org
https://thedomaindrivendesign.io
https://dotnetcodr.com/2013/09/12/a-model-net-web-service-based-on-domain-driven-design-part-1-introduction/
https://dotnetcodr.com/2015/10/22/domain-driven-design-with-web-api-extensions-part-1-notifications/
Sechseckige Architektur:
https://fideloper.com/hexagonal-architecture
https://herbertograca.com/2017/09/14/ports-adapters-architecture/
Credits
http://www.andreavallotti.tech/en/2018/01/event-sourcing-and-cqrs-in-c/
https://www.Exceptionnotfound.net/real-world-cqrs-es-with-asp-net-and-redis-part-1-overview/
https://buildplease.com/pages/fpc-1/
https://dotnetcoretutorials.com/2019/04/30/the-mediator-pattern-in-net-core-part-1-whats-a-mediator/
https://itnext.io/why-and-how-i-implemented-cqrs-and-mediator-patterns-in-a-microservice-b07034592b6d