Web

Moderne Web-Service-Architekturen

Mit CQRS Microservices beschleunigen

13.07.2023
Von   IDG ExpertenNetzwerk
Andreas Hernitscheck entwirft als Softwarearchitekt komplexe Systeme unter Verwendung von Cloud-Technologien. Als Entwicklungsleiter koordiniert er den Lebenszyklus von Software und führt Teams zum Erfolg. Das breite Wissen über verschiedene Technologien erweitert er seit 1988 bis heute ständig, um optimal zu beraten und gute Lösungen zu schaffen.
Wird Ihr Web-Service mit zunehmender Popularität und Zugriffszahlen immer langsamer? So können Sie ihn ohne zusätzliche Hardware in der notwendigen Geschwindigkeit bereitstellen.
Eine CQRS-Architektur verleiht lahmen Microservices wieder mehr Speed.
Eine CQRS-Architektur verleiht lahmen Microservices wieder mehr Speed.
Foto: Lightspring - shutterstock.com

Hat man ein Gesamtsystem aus verschiedenen Microservices aufgebaut, passiert es häufig, dass ein Microservice auf einen anderen via HTTP zugreift, um Daten aus seiner Domäne zu ziehen. Resultat sind vernetzte Strukturen, die eine eher schlechte Performance aufweisen. Zudem gibt es damit eine starre Abhängigkeit unter den Services und der Ausfall eines Microservice könnte damit auch andere blockieren.

Mit einer CQRS-Architektur (Command and Query Responsibility Segregation) können Sie feste Abhängigkeiten zwischen vorhanden Diensten auflösen und Daten wieder mit hoher Geschwindigkeit bereitstellen.

Wie funktioniert CQRS?

Um eine CQRS-Lösung zu verstehen, gehen wir von einem einfachen Beispiel aus. Stellen wir uns zwei Microservices vor; der Service "Profiles" ist zuständig für die Verwahrung der Profilinformationen von Kunden und der Service "Orders" für die Bestellungen.

Angenommen, der Service "Profiles" benötigt die Summe der Beträge aus verschiedenen Bestellungen je Kunde. Diese Information könnte per REST-Call (ein HTTP-Aufruf) vom Service "Orders" durch "Profiles" abgerufen werden.

Beim Abruf der Bestellung via HTTP wird die Summe der Beträge erst nach einer Abfrage langwierig berechnet.
Beim Abruf der Bestellung via HTTP wird die Summe der Beträge erst nach einer Abfrage langwierig berechnet.
Foto: Andreas Hernitscheck

Beim Abruf dieser Daten findet zum einen der erwähnte Zugriff via HTTP statt, der eine gewisse Zeit benötigt. Und zum anderen wird klassischerweise die benötigte Summe erst jetzt berechnet, indem über alle Bestellungen des Kunden iteriert wird, um die Werte der Bestellungen zusammenzuzählen. Dieser Vorgang kann dauern und je nachdem, ob noch weitere Informationen benötigt werden, entsprechend lange.

Ein Buchhalter würde dieses Problem lösen, indem er die Beträge in dem Moment, in dem sie anfallen, auf verschiedene Konten bucht. Damit kann er jederzeit und ohne weitere Berechnung die Summen von den Konten abrufen. Genau dieses Prinzip kann man sich mit CQRS zu eigen machen, um die Lese-Operation zu beschleunigen.

Berechnete Werte lesen

Doch welcher Service soll nun die Summe berechnen und speichern? Am Ende ist es ja der Service "Profiles", der die Summe aller Bestellungen eines Kunden benötigt, um sie wiederzugeben. Somit könnte dieser auch die Summe berechnen. Doch woher weiß der Service "Profiles", dass es etwas zu berechnen gibt, nachdem bei "Orders" eine Bestellung eingegangen ist?

Um die Neuigkeit "Eine Bestellung eines Kunden ist eingegangen" allen interessierten Services bereitzustellen, kann eine Message-Queue wie in Kafka oder SQS genutzt werden. Die Services beobachten die Message-Queue und falls sie sich "angesprochen fühlen", verwenden sie diese Information für sich. Ein weiterer Vorteil hierbei ist, dass die Verarbeitung der Nachrichten aus der Message-Queue hintereinander erfolgt und nicht parallel, womit das System so nicht überlastet wird.

Wenn also die Neuigkeit über die Bestellung eines Kunden den Service "Profiles" erreicht, könnte er diese für sich verarbeiten und in einer eigenen Datenbank ablegen. Diese darf dann eine Struktur aufweisen, die nur der Service "Profiles" bestimmt. Natürlich ist das eine Datenredundanz, die jedoch durch die Domänentrennung und dem Geschwindigkeitsvorteil legitim wird.

Bei einer CQRS-Lösung erhalten die Service "Profiles" parallel eine Benachrichtung via Message-Queue.
Bei einer CQRS-Lösung erhalten die Service "Profiles" parallel eine Benachrichtung via Message-Queue.
Foto: Andreas Hernitscheck

Praktisch gibt es eine zeitliche Verzögerung zwischen der Bestellung und der aktualisierten Summe. In der Regel liegt das jedoch im Bereich von Sekunden oder Millisekunden (je nach Systemauslastung). Für dieses Beispiel ist das nichts, was einem Benutzer auffallen würde.

Weitere Berechnungen möglich?

Nun kommt es aber zu einem Dilemma, wenn man erst später auf die Idee kommt, noch andere Werte aus den Bestellungen zu berechnen. Die Ereignisse, die zur Berechnung geführt haben, in diesem Fall also die Käufe, liegen in der Vergangenheit und eine Vor-Berechnung kann nicht mehr live durchgeführt werden.

Um das zu lösen, bedient man sich der Aufzeichnung der Bestellungen aus dem Service "Orders". Diese müssen inklusive ihres genauen Zeitpunktes (mit Millisekunden) erneut über die Message-Queue versendet werden. Die jeweiligen Empfänger vergleichen das Datum mit ihren letztem Stand der jeweiligen Berechnung und verarbeiten alle neueren Informationen.

Angenommen, eine Berechnung war fehlerhaft und in Folge nun auch die Daten in "Profiles", müssen alle Werte erneut berechnet werden. Für eine solche Korrektur benötigen Sie eine Strategie und idealerweise einen Mechanismus, der

  1. die fehlerhaften Daten entfernt (oder als ungültig erklärt) und

  2. das erneute Senden der historischen Werte veranlasst.

Da diese Prozedur möglicherweise einige Zeit dauern könnte, sollten Sie sich auch überlegen, wie sich das System in der Zwischenzeit verhält und was mit neuen Bestellungen passieren soll, die in dem Zeitraum dieser Wiederherstellung ankommen.

Je nachdem, um wieviel Daten es sich insgesamt handelt, kann der Service "Profiles" die Information über die Summe nicht nur in seiner selbst bestimmten Datenbank ablegen, sondern vielleicht auch im Arbeitsspeicher halten. Gegebenenfalls einfach nur als vorübergehender Cache. Das Entscheidende hier ist aber, dass der Cache nicht veralten kann, da der Service die Möglichkeit hat, auch diesen bei neu eintreffenden Nachrichten zu aktualisieren.

Das Ergebnis

Durch die angewendete Architektur kann der Service "Profiles" die Summen per Kunde bei Abfragen sehr schnell und ohne weitere Abfragen anderer Services und Berechnungen sofort zu Verfügung stellen.

Wenn Sie eine bestehende Anwendung haben, die eben nicht mehr schnell genug reagiert, lohnt sich vielleicht der Umbau auf diese Ereignis-getriebene Architektur mit CQRS. Die beschriebene Architektur ist nur eine Variante von CQRS und ähnliche Modelle könnten sogar noch besser zu Ihrer vorhandenen Architektur passen. Ein tieferes Befassen mit dem Thema offeriert damit vielleicht weitere interessante Möglichkeiten.

Wie so oft profitieren Sie beim Refactoring langfristig, denn ab dem Moment der Umstellung kann neuer Code die Event-getriebene Architektur ebenfalls nutzen und wird das System nicht weiter verlangsamen. (mb)