CQRS als Grundlage für moderne, flexible und skalierbare Anwendungsarchitektur

Man hört oft, komplexe Systeme basierten auf CQRS. Doch was genau ist das eigentlich, CQRS? Was verbirgt sich hinter dem Akronym, und wie kann man es nutzen?

In Pocket speichern vorlesen Druckansicht 13 Kommentare lesen
Bild einer Baustelle auf einem Laptop-Bildschirm, KI-generiert

(Bild: erzeugt mit KI durch iX)

Lesezeit: 26 Min.
Von
  • Golo Roden
Inhaltsverzeichnis

Vor zwei Wochen habe ich einen Blogpost als Einführung in Event-Sourcing geschrieben. Das Thema ist bei vielen von Ihnen auf sehr großes Interesse gestoßen, es gab sehr viel Feedback dazu, und natürlich gab es auch eine ganze Reihe von Fragen. Im heutigen Blogpost greife ich viele dieser Fragen auf und beantworte sie. Sie bekommen aber vor allem auch eine weitere Einführung in ein mit Event-Sourcing zusammenhängendes Konzept: Heute geht es nämlich auch um ein Architektur-Pattern namens CQRS. Was das ist, wofür es gut ist, wie es funktioniert, was die Anwendungsfälle sind, wie Sie es mit Event Sourcing kombinieren können, und so weiter – all das erfahren Sie heute.

the next big thing – Golo Roden

Golo Roden ist Gründer und CTO von the native web GmbH. Er beschäftigt sich mit der Konzeption und Entwicklung von Web- und Cloud-Anwendungen sowie -APIs, mit einem Schwerpunkt auf Event-getriebenen und Service-basierten verteilten Architekturen. Sein Leitsatz lautet, dass Softwareentwicklung kein Selbstzweck ist, sondern immer einer zugrundeliegenden Fachlichkeit folgen muss.

Fangen wir einmal, ganz kurz, mit einer Klarstellung an: Vielleicht haben Sie von CQRS schon gehört, und Sie würden nun gerne wissen, was genau es damit auf sich hat, aber vielleicht haben Sie den Blogpost zu Event Sourcing vor zwei Wochen nicht gelesen. Da stellt sich nun die Frage: Können Sie den heutigen Post trotzdem "einfach so" lesen, oder sollten Sie sich zuerst mit dem Thema Event Sourcing beschäftigen?

Nun, dafür ist ganz wichtig: CQRS und Event Sourcing sind zwei Konzepte, die unabhängig voneinander existieren. Das heißt, Sie können CQRS durchaus umsetzen, ohne Event Sourcing nutzen zu müssen, und Sie können Event Sourcing auch umsetzen, ohne CQRS nutzen zu müssen, und insofern könnten Sie diesen Blogpost auch als in sich geschlossenes Thema ansehen.

Aber: Die beiden Konzepte ergänzen einander ausgesprochen gut. Insofern werden Sie sie in der Praxis eher selten isoliert voneinander vorfinden, sondern in der Regel gemeinsam. Deshalb baue ich in diesem heutigen Blogpost auf dem auf, was ich vor zwei Wochen geschrieben habe. Sollten Sie jenen Blogpost also noch nicht gesehen haben, dann würde ich Ihnen empfehlen, das jetzt nachzuholen. Alternativ können Sie ihn sich auch als Video ansehen.

Doch was ist CQRS überhaupt? Das Akronym steht für "Command Query Responsibility Segregation", also für die Trennung der Verantwortlichkeiten für Commands und Queries.

Tatsächlich ist das sehr viel einfacher, als es zunächst klingt. Die Kernidee von CQRS ist, dass es für den Zugriff auf Anwendungen nur zwei Arten von Interaktionsmustern gibt: Entweder führt die Anwenderin oder der Anwender eine Aktion aus, mit der Absicht, im System etwas zu verändern, oder sie oder er möchte etwas wissen. Mehr Möglichkeiten gibt es nicht. Das heißt, entweder teilen Sie einer Software mit, dass sie dies oder jenes für Sie erledigen soll, dann geben Sie ihr quasi einen Befehl (das ist dann ein sogenannter "Command") – oder Sie fragen die Software nach einer Information, Sie stellen also eine Frage oder führen eine Abfrage durch (und das ist dann eine sogenannte "Query").

Empfohlener redaktioneller Inhalt

Mit Ihrer Zustimmung wird hier ein externes YouTube-Video (Google Ireland Limited) geladen.

Ich bin damit einverstanden, dass mir externe Inhalte angezeigt werden. Damit können personenbezogene Daten an Drittplattformen (Google Ireland Limited) übermittelt werden. Mehr dazu in unserer Datenschutzerklärung.

Das eine, also ein Command, zieht üblicherweise eine Veränderung und damit einen Schreibvorgang nach sich, das andere, also eine Query, greift hingegen nur lesend zu. Man könnte also auch sagen: Es gibt schreibende und lesende Zugriffe. Das CQRS-Entwurfsmuster besagt nun, dass Sie Ihre Anwendung zweiteilen sollten: in einen Teil, der sich um Schreibvorgänge kümmert, und in einen anderen Teil, der sich um Lesevorgänge kümmert. Sie sollen also, und genau das war meine ursprüngliche Aussage, die Verantwortlichkeit für das Schreiben von der für das Lesen trennen.

Worüber dieses Design-Pattern jedoch nichts aussagt, ist, auf welche Art Sie diese Trennung bewerkstelligen sollen. Es ist also erst einmal völlig offen, ob das einfach nur bedeutet, dass Sie gedanklich zwischen schreibenden und lesenden API-Routen trennen, oder ob Sie zwei getrennte APIs bauen, oder ob das zwei getrennte Services sind, oder sonst etwas. Das ist, wie es im Englischen so schön heißt, "up to interpretation". Darauf kommen wir nachher jedoch näher zu sprechen.

Die erst einmal viel spannendere Frage ist: Warum sollte man das überhaupt machen? Also, warum gibt es diese Empfehlung in Form des CQRS-Patterns überhaupt?

Nun, dahinter steckt eine sehr einfache Überlegung, und sie hat erst einmal sehr wenig mit APIs zu tun, sondern viel mehr mit Datenbanken: Wenn wir über klassische Architektur sprechen, haben wir normalerweise eine UI, eine irgendwie geartete API und am Ende eine Datenbank. Obwohl die Datenbank dabei buchstäblich nicht im Mittelpunkt der Entwicklung steht, beschäftigen wir uns in der Softwareentwicklung üblicherweise sehr ausführlich mit ihr, und häufig hat das in der Datenbank gewählte Datenmodell deutliche Auswirkungen darauf, wie die Geschäftslogik in der API gebaut wird. Das heißt, eine essenzielle Frage lautet: Wie sieht ein gutes Datenmodell aus?

Die akademische Welt hat darauf eine passende Antwort: In der Datenbanktheorie gibt es nämlich verschiedene Normalformen. Das kann man sich so ein bisschen vorstellen wie einen Satz von Regeln, wie man Datenmodelle gestalten sollte. Konkret gibt es davon fünf verschiedene Varianten, und die fünfte Normalform entspricht ein wenig dem heiligen Gral: Wenn man sie umsetzt, dann bedeutet das nichts anderes, als dass alles sehr sauber und ordentlich strukturiert ist, dass keinerlei Information dupliziert wird, dass es keine Redundanzen in den Daten gibt, und so weiter. Das ist deshalb erstrebenswert, weil sich dann Konsistenz- und Integritätsregeln sehr leicht umsetzen lassen. Die fünfte Normalform ist so gesehen also ein Traum, wenn es darum geht, Daten zu schreiben.

Ein wenig unglücklich ist nur, dass man Daten in der Regel auch wieder lesen muss. Das ist in der fünften Normalform natürlich möglich, nur muss man hierfür meist sehr komplexe Abfragen ausführen, und man braucht dann, um zum Beispiel etwas Simples wie die Stammdaten einer Anwenderin auszulesen, auf einmal 27 Joins. Das heißt, Lesen ist durchaus machbar, aber es ist völlig ineffizient und langsam. Das ist natürlich sehr unpraktisch und im Alltag wenig nützlich. Also, kurz gesagt: Die fünfte Normalform ist gut zum Schreiben geeignet, aber eine Katastrophe zum Lesen.

Das andere Extrem wäre die erste Normalform: Hier wird einfach für jede View, die es in der UI gibt, eine passende Tabelle angelegt, und die Daten werden überall dorthin verteilt, wo man sie gerade braucht. Das heißt, die Daten werden komplett denormalisiert, was das Lesen rasend schnell macht: Denn mehr als ein SELECT * FROM xy ist dann in der Regel nicht erforderlich, schließlich gibt es ja per Definition für jede View eine passende Tabelle. Insofern ist die erste Normalform ein Traum, wenn es um das Lesen von Daten geht.

Allerdings ist das Schreiben in diesem Fall eher schwierig, denn man muss dieselbe Information mehrfach ablegen, man muss sie mehrfach pflegen, und es ist deshalb sehr kompliziert, Konsistenz und Integrität zu wahren. Man könnte also festhalten, dass die erste Normalform genau das Gegenteil der fünften ist: Die erste ist gut zum Lesen, aber schlecht zum Schreiben.