DDD & Co., Teil 7: CQRS
Das Verarbeiten von Kommandos und Erzeugen der fachlichen Ereignisse ist seit der vergangenen Folge in semantisch sinnvollem Code abgebildet. Auf dem Weg lassen sich die Konsistenz und IntegritÀt effizient sicherstellen, auch das Speichern der Ereignisse passt dank Event Sourcing konzeptionell dazu. Wie steht es um das Lesen der Daten?
Das Verarbeiten von Kommandos und Erzeugen der fachlichen Ereignisse ist seit der vergangenen Folge [1] in semantisch sinnvollem Code abgebildet. Auf dem Weg lassen sich die Konsistenz und IntegritÀt effizient sicherstellen, auch das Speichern der Ereignisse passt dank Event Sourcing [2] konzeptionell dazu. Wie steht es um das Lesen der Daten?
Aggregate kĂŒmmern sich um die Konsistenz [3] der Daten in einem dedizierten Bereich der Anwendung. Sie verarbeiten Kommandos und erzeugen fachliche Ereignisse, die in einem Event Store persistiert werden können. Replays und Snapshots ermöglichen den raschen Zugriff auf alle Ereignisse eines Aggregats.
Das funktioniert zwar, legt den Schwerpunkt aber auf jene VorgĂ€nge, die den internen Zustand der Anwendung verĂ€ndern. Das gilt aber nicht fĂŒr jeden Zugriff: Werden ausschlieĂlich Daten gelesen, indem beispielsweise Abfragen ausgefĂŒhrt werden, wird kein Zustand verĂ€ndert.
Die Arbeit mit dem Event Store weist auĂerdem auch einige Probleme auf. So ist der Zugriff auf die fachlichen Ereignisse beispielsweise nur an Hand der ID des Aggregats möglich. Eine Liste aller Aggregate ist zunĂ€chst nicht vorgesehen.
Eine solche ist aber in vielen FĂ€llen unabdingbar â beispielsweise um in der TodoMVC-Anwendung eine Liste der offenen oder der bereits erledigten Aufgaben anzuzeigen. In einem Satz lĂ€sst sich daher zusammenfassen, dass die bisher vorgestellten Konzepte gut zum Schreiben von Daten passen, jedoch weniger zum Lesen geeignet sind.
DDD trifft CQS
Das deckt sich mit dem Entwurfsmuster CQS (Command Query Separation), das von Bertrand Meijer erdacht wurde. Es besagt, dass jede Funktion eines Objekts entweder als Command oder als Query entworfen sein soll.
Der Unterschied zwischen Commands und Queries besteht darin, dass Commands den Zustand eines Objekts verĂ€ndern und keine Daten zurĂŒckgeben, wogegen Queries sich genau gegenteilig verhalten: Sie dienen dazu, den Zustand eines Objekts abzufragen, dĂŒrfen ihn aber nicht verĂ€ndern.
Die bislang vorgestellten Konzepte von DDD machen deutlich, dass sich Aggregate zwar um Kommandos (Commands) kĂŒmmern, nicht jedoch um Abfragen (Queries). Das ist wenig verwunderlich, denn Abfragen haben keinen Einfluss auf die transaktionalen Konsistenzgrenzen der Anwendung.
Aus den zuvor genannten GrĂŒnden eignet sich ein Event Store allerdings auch kaum fĂŒr Abfragen. Zwar lassen sich auf Basis der gespeicherten fachlichen Ereignisse zahlreiche Fragen beantworten, das erfordert aber eine aufwierige und zeitaufwĂ€ndige Analyse. Das mag fĂŒr Reports akzeptabel sein, aber nicht fĂŒr den tagtĂ€glichen Betrieb, in dem Daten möglichst performant zur VerfĂŒgung stehen mĂŒssen.
CQS fĂŒr die Anwendungsarchitektur
Die Lösung besteht darin, das zunĂ€chst auf einzelne Objekte bezogene Entwurfsmuster CQS auf die gesamte Anwendungsarchitektur zu ĂŒbertragen. Eine Anwendung besteht dann nicht mehr aus einem einzigen System, das sich um das Schreiben und Lesen von Daten gleichermaĂen kĂŒmmert. Stattdessen wird sie in zwei Teilsysteme zerlegt, die jeweils auf einen der beiden VorgĂ€nge spezialisiert sind.
Der Ansatz wird als CQRS (Command Query Responsibility Seggregation) bezeichnet. Wie auch beim Event Sourcing gilt, dass CQRS und DDD unabhÀngig voneinander eingesetzt werden können, sie aber hervorragend zueinander passen.
Die Idee ist, den Event Store um eine zweite Datenbank zu ergĂ€nzen, die gezielt auf das Lesen ausgelegt wird. Da die Konsistenz bereits von den Aggregaten in Verbindung mit dem Event Store sichergestellt wird, muss die Lesedatenbank nicht zwingend normalisiert werden. Das bedeutet im Umkehrschluss, dass sie denormalisiert werden darf, wenn das fĂŒr das effiziente AusfĂŒhren von Abfragen sinnvoll ist.
Dedizierte Lesetabellen
So könnte es beispielsweise eine dedizierte Tabelle geben, die alle offenen Aufgaben der TodoMVC-Anwendung enthÀlt. Die zugehörige Abfrage reduziert sich auf dem Weg auf die folgende, simple SQL-Anweisung:
SELECT * FROM noted_todos
Das gleiche gilt fĂŒr alle denkbaren Abfragen. Komplizierter als ein einfaches SELECT, höchstens ergĂ€nzt um ein WHERE oder ORDER BY, sollten sie nicht sein. Auf die Art entfĂ€llt insbesondere jegliche Notwendigkeit, Abfragen mit Hilfe von JOIN ĂŒber mehrere Tabellen auszufĂŒhren.
Das fĂŒhrt zu ausgesprochen effizienten LesevorgĂ€ngen. Da die Lesedatenbank zudem nicht fĂŒr das Sichern der Konsistenz zustĂ€ndig ist, lĂ€sst sie sich problemlos replizieren. Kann eine Instanz nicht lĂ€nger die gewĂŒnschte Leseleistung erbringen, kann das Lesen auf dem Weg praktisch beliebig skaliert werden.
Schreiben und lesen synchronisieren
Das alles wirft lediglich die Frage auf, wie die Schreib- und die Leseseite der Anwendung synchronisiert werden. Dazu dient ĂŒblicherweise ein eigener Prozess, der auf fachliche Ereignisse reagiert und die Lesetabellen gemÀà der gewĂŒnschten Interpretation aktualisiert.
Ob ein Ereignis in einer Lesetabelle dabei als INSERT, UPDATE oder DELETE verarbeitet wird, hĂ€ngt von der jeweiligen Semantik ab. Letztlich kommt hier also tatsĂ€chlich wieder CRUD ins Spiel, allerdings in entschĂ€rfter Form, da es nur noch dem Aktualisieren der Lesetabellen dient. Die fachliche KonsistenzprĂŒfung findet mit Hilfe der fachlichen Ereignisse und der Aggregate statt.
Dass das Vorgehen Seiteneffekte aufweist, liegt auf der Hand. Immerhin vergeht eine gewisse (wenn auch kurze) Zeit zwischen dem Schreiben der fachlichen Ereignisse im Event Store und dem Aktualisieren der Lesetabellen. Welche Auswirkungen das hat und was dabei zu berĂŒcksichtigen ist, wird Thema der nĂ€chsten Folge sein.
tl;dr: Das Entwurfsmuster CQRS beschreibt das Trennen der Schreib- von der Leseseite einer Anwendung. CQRS und DDD gehören nicht zwingend zusammen, ergÀnzen einander aber ausgezeichnet, weshalb sich ihre Kombination in der Praxis hÀufig anbietet. ( [10])
URL dieses Artikels:
https://www.heise.de/-3798868
Links in diesem Artikel:
[1] https://www.heise.de/developer/artikel/DDD-Co-Teil-6-Vom-Modell-zum-Code-3793666.html
[2] https://www.heise.de/developer/artikel/DDD-Co-Teil-5-Event-Sourcing-3780773.html
[3] https://www.heise.de/developer/artikel/DDD-Co-Teil-4-Aggregates-3780746.html
[4] https://www.heise.de/blog/TodoMVC-mit-DDD-Teil-1-Was-spricht-gegen-CRUD-3756224.html
[5] https://www.heise.de/blog/DDD-Co-Teil-2-xxx-3762025.html
[6] https://www.heise.de/blog/DDD-Co-Teil-3-xxx-3767669.html
[7] https://www.heise.de/blog/DDD-Co-Teil-4-xxx-3780746.html
[8] https://www.heise.de/blog/DDD-Co-Teil-5-Event-Sourcing-3780773.html
[9] https://www.heise.de/developer/artikel/DDD-Co-Teil-6-Vom-Modell-zum-Code-3793666.html
[10] mailto:webmaster@goloroden.de
Copyright © 2017 Heise Medien