Blog

FIS-SST

CQRS mit MediatR-Bibliothek in .NET Core

Sebastian Gerst

In diesem Artikel stelle ich die CQS- und CQRS-Muster vor und zeige, wie einfach es ist, das CQRS-Muster beim Schreiben von Anwendungen in .NET Core mit der MediatR-Bibliothek anzuwenden.

1.       Was sind CQS und CQRS?

CQS (Command Query Separation) ist ein 1986 von Bertrand Meyer vorgestelltes Konzept, das besagt, dass jede Systemmethode als Command (Befehl) oder Query (Anfrage) klassifiziert werden kann.

– Command (Befehl) – Methode, die den Zustand der Anwendung ändert und keinen Wert zurückgibt

– Query (Abfrage) – Methode, die einen Wert zurückgibt, aber den Zustand der Anwendung nicht ändert

CQRS (Command Query Responsibility Segregation) ist ein von Greg Young und Udi Dahan vorgestelltes Konzept, das eine Weiterentwicklung von CQS ist (CQRS verwendet das CQS-Konzept). Bei CQRS geht es um getrennte Modelle für Schreib- und Lesevorgänge. Es wird empfohlen, eine getrennte Datenbank für Lese- und Schreibvorgänge zu haben, aber es ist nicht erforderlich. In Abbildung 1 ist ein Beispielschema für eine Anwendung dargestellt, die das CQRS-Muster verwendet.


Abb. 1: Beispielschema für ein System, das das CQRS-Muster verwendet

2.       Datensynchronisierung zwischen Datenbanken zum Lesen und Schreiben

Die Datensynchronisierung zwischen einer Datenbank für Lese- und Schreibvorgänge ist eines der Hauptprobleme, mit denen wir bei der Verwendung des CQRS-Musters in Anwendungen konfrontiert werden. Zunächst einmal ist das CAP-Theorem zu erwähnen, das besagt, dass es für verteilte Datenspeicher unmöglich ist, mehr als zwei von drei zu bieten:

– Einheitlichkeit – jeder Lesevorgang gibt den letzten Schreibvorgang oder Fehler zurück,

– Verfügbarkeit – jede Anfrage erhält eine positive Antwort, aber es ist nicht garantiert, dass die Antwort die neuesten Schreibvorgänge enthält,

– Teilungstoleranz – das System arbeitet weiter trotz einer beliebigen Anzahl von Kommunikationsausfällen zwischen den Knoten im System.

Bei der Verwendung des CQRS-Musters müssen wir uns des genannten Theorems bewusst sein, da es keine CAP-Probleme löst. CQRS erlaubt es uns jedoch, unabhängig zu entscheiden, was für uns auf der Schreib- und Leseseite wichtig ist. Zum Beispiel können wir für das System entscheiden, dass es auf der Schreibseite ACID (Atomic, Consistent, Isolated, Durable) und auf der Leseseite BASE (Basic Availability, Soft-State, Eventually Consistent) konform ist.

3.       Was ist MediatR?

MediatR ist eines der beliebtesten NuGet-Pakete (https://github.com/jbogard/MediatR). MediatR hilft beim Schreiben von Anwendungen, die das Mediator-Designmuster und CQRS verwenden.

4.       Was sind die Vor- und Nachteile der Verwendung von?

CQRS löst, wie jedes andere Muster auch, spezifische Probleme und hat sowohl Vor- als auch Nachteile, die im Folgenden dargestellt werden.

Vorteile:

– Dank der Trennung werden wir keine komplexen Modellklassen mehr haben. Wir würden ein Modell für jede Datenoperation haben, und das gibt uns eine Menge Flexibilität.

– Die Trennung von Schreib- und Lesetätigkeit ermöglicht es uns, die geeignete Datenbanktechnologie für die Aufgabe zu wählen. Wir können SQL-Datenbanken zum Schreiben und Nicht-SQL-Datenbanken zum Lesen verwenden. Die zweite Möglichkeit ist die Verwendung desselben Typs, z. B. einer SQL-Datenbank, von der eine für das Lesen und eine für das Schreiben optimiert wird. Dies ist in MSSQL möglich. Eine für das Lesen optimierte Datenbank würde mehr Verzeichnisse haben und die Datenredundanz wäre hoch. Da der Schreibvorgang schnell sein muss, sollte eine Struktur ohne den Overhead von Indizes verwendet werden..

– Bei Verwendung einer separaten Datenbank für Lese- und Schreibaktivitäten können wir sie unabhängig voneinander skalieren.

Nachteile:

– Am Ende haben Sie viel mehr Codezeilen, als wenn Sie CQRS nicht verwenden, und deshalb ist es nicht empfehlenswert, dieses Muster für einfache CRUD-Anwendungen zu verwenden.

– Das macht das ganze System komplex. – Bei der Verwendung einer separaten Datenbank zum Lesen und Schreiben muss die Datenkonsistenz in Datenbanken gewährleistet sein.

5.       Beispielhafte Umsetzung

In diesem Artikel stelle ich eine einfache CQRS-Implementierung vor. Bei dieser Lösung wird dieselbe Datenbank für Lese- und Schreibvorgänge verwendet, aber sie kann leicht geändert werden, um sie zu trennen.

Zunächst wurde ein neues Projekt erstellt und die NuGet-Pakete wurden installiert.

nuget install MediatR

nuget install MediatR.Extensions.Microsoft.DependencyInjection

nuget install Microsoft.EntityFrameworkCore

nuget install Microsoft.EntityFrameworkCore.Design

nuget install Microsoft.EntityFrameworkCore.SqlServer Die erstellte Lösung besteht aus 5 Projekten, wie in Abbildung 2 dargestellt.


Abb. 2: Projekte in Lösung

Im CQRS. Anwendungsprojekt dienen einige Schnittstellen als Abstraktion über Befehl (command)  und Abfrage (query). Bei diesen Schnittstellen handelt es sich um einfache leere Schnittstellen, die als „Marker-Schnittstellen“ bezeichnet werden (Abb. 3).


Abb. 3: Marker interfaces

In diesem Projekt gibt es auch leere Handler-Schnittstellen, die die IRequestHandler-Schnittstelle aus der MediatR-Bibliothek umhüllen. Sie sind für die Bearbeitung entsprechender Abfragen und Handler zuständig (Abb. 4).


Abb. 4: Schnittstellen, die die IRequestHandler-Schnittstelle umhüllen

Damit dies funktioniert, muss MediatR in einer Startup-Klasse registriert werden. Dadurch wird jeder Befehl und jede Anfrage, die im CQRS.Infrastructure-Projekt platziert werden, registriert (Abb. 5).


Abb. 5: Registration von MediatR in Startup

In diesem Beispiel wurde mit Entity Framework Core eine Datenbank erstellt, die eine Tabelle mit Produkten enthält. Das Produktmodell ist in Abbildung 6 dargestellt.


Abb. 6: Produktklasse

5.1     Befehl erstellen

Nachdem wir IQuery, ICommand und die entsprechenden Handler definiert haben, können wir mit der Erstellung von Befehlen und Abfragen in unserem System beginnen, die es uns ermöglichen, Produkte zu verwalten. Einer der erstellten Befehle in unserem System ist ein Befehl, der für das Hinzufügen von Produkten zur Datenbank verantwortlich ist (Abb. 6). Dieser Befehl implementiert die ICommand-Schnittstelle und enthält Daten, die an den Command-Handler übergeben werden.


Abb. 6: AddProductCommand-klasse

Wenn ein Befehl erstellt wird, können wir eine Klasse AddProductCommandHandler (Abb. 7) erstellen, die den Befehl AddProductCommand verarbeitet. Diese Klasse muss die Schnittstelle ICommandHandler implementieren. Die Befehlsklasse, die verarbeitet wird, muss als generischer Parameter an ICommandHandler weiter gegeben werden.


Add. 7: AddProductCommandHandler-klasse

5.2     Abfrage erstellen

Im vorigen Schritt haben wir einen Befehl und einen Befehls-Handler für das Hinzufügen von Produkten zur Datenbank erstellt. Um Daten aus unserem System an den Benutzer zurückzugeben, müssen wir eine Abfrage erstellen.

In Analogie zum Befehl erstellen wir eine Klasse GetProductsQuery (Abb. 8), die die Schnittstelle IQuery implementiert und dieser Schnittstelle einen generischen Parameter übergibt, der einen Rückgabetyp darstellt.


Abb. 8: GetProductsQuery-klasse

Um diese Abfrage zu bearbeiten, erstellen wir einen GetProductsQueryHandler (Abb. 9). Diese Klasse implementiert die Schnittstelle IQueryHandler mit zwei Parametern:

a) Klasse, die die zu bearbeitende Anfrage repräsentiert – in unserem Fall GetProductsQuery

b) Rückgabetyp – IEnumerable<ProductDTO>


Abb. 9: GetProductsQueryHandler-klasse

Da wir einen Befehl und eine Abfrage erstellt und diese Aktionen verarbeitet haben, können wir sie im Controller verwenden (Abb. 10) und testen, ob alles korrekt funktioniert.


Fig. 10 ProductsController-klasse

6.       Zusammenfassung

Wie im Beispiel gezeigt, ist es einfach, das CQRS-Muster mithilfe der MediatR-Bibliothek in .NET Core zu implementieren. Mit etwas Zeitaufwand ist es auch möglich, diese Lösung so zu modifizieren, dass eine separate Datenbank für Lese- und Schreibvorgänge verwendet wird, aber die Synchronisierung zwischen den Datenbanken muss vom Entwickler durchgeführt werden. Wie auch in diesem Beispiel gezeigt, besteht eine einfache CRUD-Anwendung, die nach dem CQRS-Muster geschrieben wurde, aus einer Menge von Klassen und Codezeilen, weshalb die Verwendung dieses Musters in CRUD-Anwendungen nicht empfohlen wird.

In unserem Unternehmen hatten wir ein Projekt, bei dem das CQRS-Muster verwendet wurde. Als ich und andere Mitarbeiter diesem Team beitraten, war es für uns als neue Teammitglieder einfach zu verstehen, was im Anwendungscode passiert, und neue Funktionalitäten zu schreiben und neu hinzugefügten Code an dieses Muster anzupassen.

Kontaktformular

Lukas Schikora

Project & Operations Manager

Bitte zögern Sie nicht, mich telefonisch oder per E-Mail zu kontaktieren

Download

Damit der Download erfolgen kann, geben Sie bitte hier Ihren Namen und Ihre E-Mail Adresse ein.

Unternehmensinformationen herunterladen (PDF)

Damit der Download erfolgen kann, geben Sie bitte hier Ihren Namen und Ihre E-Mail Adresse ein.

Grenzüberschreitendes Unterstützungskonzept

Damit der Download erfolgen kann, geben Sie bitte hier Ihren Namen und Ihre E-Mail Adresse ein