CQRS – Ein Ritt Querbeet

Auf dem .NET Open Space in Leipzig habe ich einige Jünger des CQRS erlebt. Fast etwas fanatisch, aber vielleicht ist das ja ein gutes Zeichen. Egal; das Akronym steht jedenfalls für das sperrige Wort Command Query Responsibilty Segregation.

Es mag ein Pattern sein und wird auch so von unserem Pattern-Archivar Martin Fowler in seinem Bliki erläutert. Eigentlich ist es ein ganzes Pattern-Set und ein grundlegendes fundamentales Architektur-Paradigma, welches einschneidender als die objektorientierte Programmierung ist!

Deswegen plädiere ich auch für eine eigene Sprache für CQRS – eine sprachbasierte Abstraktion, welche den Pattern-Aufsatz auf OO-Sprachen ersetzt.

..tja jetzt kommt schon die Diskussion vor der Erläuterung. Also was ist eigentlich CQRS?

  • die Trennung von Lese und Schreibvorgängen
  • Eventbasierte Programmierung
  • Taskbasierte UI (und Programmierung)
  • Event Sourcing und der persistente Event Log

Bevor man sich mit diesen “Design-Elementen” des CQRS beschäftigt erst einmal zu den Gründen für dessen Einsatz. Was ist schlecht daran eine relationale Datenbank über einen ORM an ein OO-Domänenmodell anzubinden und so die Anwendung zu implementieren?

Ganz einfach: Performance.

Es gibt ja so eine Grundregel, die besagt, dass von Verteilbarkeit, Performanz und Integratität  gleichzeitig nur 2 Aspekte erfüllt werden können. Eine verteilte Datenbank, welche die Integrität (in diesem Fall die Fremdschlüsselbeziehungen) wahrt, ist nicht performant. Eine performante verteilte Architektur hingegen muss auf die Integrität verzichten. Und eine performante und integere Datenbank ist möglich, aber ohne Verteilbarkeit.

Relationale Datenbanken sind stark wenn es um die Integrität geht, aber bei den anderen 2 Aspekten punkten sie nicht so gut. Hier setzt das CQRS-Prinzip an und entkoppelt Lese- und Schreibvorgänge voneinander. Im simpelsten Fall finden die Lesevorgänge weiter aus einer relationalen Datenbank statt, aber dies ist kein Muss. Die Struktur die Lesedatenbank muss in keiner Weise normalisiert sein. Vielleicht ist die Lesedatenbank eine Dokumentendatenbank mit serialisierten Objektgraphen. Vielleicht enthält die Lesedatenbank schon View-Models eines MVVM oder vielleicht sogar schon das partielle HTML für eine Website. Kurz gesagt: eine Lesedatenbank kann für die Performance so optimiert werden, wie die Anforderungen und Last es nötig machen und das logisch unabhängig von den Schreibvorgängen.

OK. Denormalisierung und Performance klingt ja alles gut. Wie kommen denn aber die Daten nun in diese Lesedatenbank hinein?

Die Daten kommen natürlich aus der Schreibdatenbank. Über die Struktur der Schreibdatenbank werden wir uns noch unterhalten…

Ja – die Daten der Schreibdatenbank werden repliziert. CQRS ist jedoch weit mehr als ein Replizierungsmechanismus, denn die Kopplung ist wichtig. Die Anwendung muss beim Schreiben nicht die Beschaffenheit der Lesedatenbank kennen. Sie weiß lediglich, wo in die Schreibdatenbank eh… geschrieben werden muss.

Die Schreibdatenbank kennt auch nicht die Struktur der Lesedatenbank. Die Lesedatenbank weiß jedoch um die Struktur der Schreibdatenbank. Aha! Hier ist also das Geheimnis!

Die Lesedatenbank schickt Event Handler auf die Pirsch, die Schreibvorgänge abfangen und die Lesedatenbank mit den enthaltenen Daten aktualisieren. Also z.B. ordentlich denormalisiert.

Das war alles noch recht trocken. Es geht zwar auch so, aber süffiger wird es mit sogenannter Eventbasierter Programmierung und Event Sourcing.

Nehmen wir an, diese Schreibdatenbank ist gar keine Datenbank, wie wir Sie kennen: Entities mit Attributen und Beziehungen, sondern einfach ein Event Store, d.h. eine lange Liste von Events, die jemals vorgefallen sind: Kunde 1 wurde angelegt, Kunde 1 wurde freigeschalten, Kunde 1 hat Bestellung 1 erstellt, Kunde 2 wurde angelegt, Kunde 2 wurde gelöscht, ….

Der Event Store speichert alle Daten, die “EventArgs”, zu einem Event ab, d.h. fügt Sie dem Event Store an. Somit ist der Event Store ein “Append Only File”. Append Only ist eine geniale Eigenschaft zur Replizierung. Man kann diese Daten überall hinschicken. Sie werden niemals “stale”. Höchstens überholt.

Durch die Event Handler wird die Art der Replizierung bestimmt. Wird nach einem Write in die Schreibdatenbank sofort die Lesedatenbank in einer Transaktion aktualisiert oder genügt eine “Eventual Consistency”, d.h. irgendwann wird sich die Lesedatenbank schon wieder aktualisiert haben.

OK – Geheimnis gelöst. Skalierbarkeit jederzeit gigantisch erhöhbar.

Nur gibt es jetzt noch ein letztes Problem. Wie bekommen wir denn Events in den Event Store? Leider müssen wir unser gesamtes OO-Programmiermodell dafür umstellen. Verdammt – CQRS ist also nichts, mit dem man seine Architektur mal so anreichern kann. Es ist eine fordernde Diva, der man seine ganze Architektur widmen muss!

Die Business Logik muss nach Use-Cases gruppiert sein (oder “Tasks”). Ein Task sollte ein semantischer Vorgang sein – in den Worten eines Open Space Teilnehmers – nicht “Kunde aktualisieren”, sondern “Kunde zieht um”. Somit entfällt jegliche typische CRUD-Architektur!

Eine UI ruft weiterhin die Business Logik auf, aber die Business Logik speichert die Daten nicht. Es gibt keine Save Methode. Wichtig ist v.a.: übliche Business Logik ruft mehrere Save-Methoden für Objekt-Graphen auf. In CQRS erstellt ein Business Logik Task je ein Business Logik Event, welches persistent an den Event Store angefügt wird. Was dann passiert, ist für die Business Logik nicht vorhersehbar. Vielleicht wird das Schreibevent von niemandem konsumiert. Vielleicht stürzen sich jedoch auch 100 Event Handler darauf, die es in viele verschiedene Read-Datenbanken transformieren!

Das war mein wilder Ritt durch CQRS. Nicht besonders strukturiert, aber das Wesentliche betonend. Falls Sie jetzt nur Bahnhof verstanden haben, schauen Sie mal bei Martin Fowler nach und versuchen Sie es systematisch zu verstehen und dann lesen Sie noch mal meinen Blog-Eintrag und verstehen dann, was das ganze wirklich bedeutet!