Ereignisgesteuerte Architektur: Grundlagen, Vorteile und Herausforderungen
Softwarearchitekturen ähneln oft Kommunikationsstrukturen, wie wir sie auch außerhalb von Software in physischen Systemen finden. Systeme haben Zustände, die sich ändern können. Solche Zustandsänderungen können wir als Ereignisse betrachten, die sich als Nachrichten an andere Teile des Systems übermitteln lassen. Und diese anderen Teile können darauf reagieren, indem sie Wissen anreichern oder Prozesse auslösen. Geschäftsprozesse in Organisationen lassen sich dadurch effizient abbilden und automatisieren. Das ist ein Grund für die Beliebtheit ereignisgesteuerter Architektur.
Vorteilhafte Grundlagen

Stellen wir uns einen Kunden vor, der eine Pizza von seiner Lieblingspizzeria bestellen möchte. Also ruft er dort an, bestellt beim Kellner und erhält eine Weile später die Pizza. Das auslösende Ereignis ist hier die telefonische Bestellung des Kunden. Betrachten wir den ausgelösten Prozess in der Pizzeria genauer, fallen uns weitere Ereignisse auf. Der Kellner schreibt einen Bestellzettel und hängt diesen an eine Pinnwand in der Küche. Auf dieses Ereignis reagiert ein Koch, backt die Pizza und legt diese verpackt am Warenausgang ab. Auf das Ereignis in Form der Pizza am Warenausgang reagiert ein Bote, der die Bestellungen ausliefert, womit der Geschäftsprozess endet.
In diesem Beispiel erkennen wir die grundlegenden Eigenschaften ereignisgesteuerter Architektur. Der Geschäftsprozess wird über Ereignisse gesteuert und von verschiedenen Ereignisprozessoren ausgeführt. Diese bearbeiten Teilaufgaben und lösen neue Ereignisse aus, auf welche gegebenenfalls weitere Ereignisprozessoren reagieren. Dabei verläuft die Kommunikation zwischen den Ereignisprozessoren asynchron.
Asynchron bedeutet in der Pizzeria, dass der Kellner nicht beim Koch darauf wartet, bis dieser die Pizza gebacken hat. Es kann auch sein, dass der Koch nicht sofort auf den Zettel reagiert, wenn sich eine Warteschlange von Zetteln auf der Pinnwand gebildet hat und andere Bestellungen zuerst an der Reihe sind. Der Koch wartet auch nicht darauf, dass der Bote am Warenausgang vorbeikommt, um ihm die Pizza abzunehmen, sondern widmet sich den nächsten Zetteln an der Pinnwand.
Dadurch arbeiten die Beteiligten zeitlich voneinander entkoppelt, wodurch sie Wartezeiten vermeiden und stattdessen produktiv arbeiten. Im Vertrauen auf den nächsten Beteiligten im Ablauf, belassen sie es dabei Ereignisse zu produzieren derer sich derjenige, den sie betreffen, schon annehmen wird.
Damit die Pizzeria wirtschaftlich arbeitet, sollten die Arbeiter gut ausgelastet sein. Die Last in einem Zeitraum wird bestimmt durch die Anzahl und Größe der Bestellungen. Diese können sich mit der Zeit ändern. Im Tages- und Wochenverlauf schwankend oder bei wachsender Beliebtheit bei der Kundschaft langfristig zunehmend.
Es könnte der Fall eintreten, dass Lastspitzen den Boten an seine Grenzen bringen, sodass er bei vielen Bestellungen in kurzer Zeit nicht mehr mit dem Ausliefern hinterherkommt. Was würde passieren? Der Koch wartet nicht auf den Boten und backt weiter Pizzen, die sich am Warenausgang zwischen den Fahrten des Boten ansammeln. Dadurch steigt die Dauer des Auslieferns nach dem Backen und damit die Wartezeit des Kunden. Bei einer Lastspitze von kurzer Dauer ginge der Pizzastau am Warenausgang rasch wieder zurück. Der Bote wäre zwar überlastet, liefert aber weiterhin verläßlich so viele Bestellungen aus, wie er kann, während einige etwas länger als üblich am Warenausgang verbleiben.
Dadurch ist das System resilient gegenüber Lastspitzen: ein Vorteil ereignisgesteuerter Architekturen. Würde nicht asynchron, sondern synchron kommuniziert, könnten die Antworten so lange dauern, dass die Kommunikation fehlschlägt und Fehler auftreten. Bei ereignisgesteuerten Architekturen passiert das eher nicht. Voraussetzung dafür in Rechnersystemen ist ein stabiles Netzwerk und ein ausreichend dimensionierter Nachrichtenbroker, der die Ereignisse der verteilten Ereignisprozessoren verwaltet und so deren Kommunikation ermöglicht.
Wenn ein Ereignisprozessor nicht nur kurzzeitig, sondern dauerhaft überlastet ist, kann es zu problematischem Verzug kommen. Wenn zum Beispiel über den ganzen Freitagabend die Zahl der Bestellungen viel höher ist als an anderen Wochentagen, könnte der Pizzastau am Warenausgang im Laufe des Abends so groß werden, dass die hohen Wartezeiten den Kunden nicht mehr zumutbar wären. Für die Kundenzufriedenheit wäre das schlecht. Was tun?
Die naheliegende Lösung: Was ein Bote nicht schafft, schaffen zwei! Also wird für den Freitagabend ein zusätzlicher Bote eingesetzt, was die Lieferrate verdoppelt und den Pizzastau am Warenausgang und die Wartezeiten der Kunden klein hält. Den Kunden kann es egal sein, wer die Pizza bringt. Am Inhalt der Lieferung ändert das nichts. Und für den Koch ändert sich auch nichts. Er kann weiterarbeiten wie gewohnt. Genauso könnte man die Zahl der Köche erhöhen, sollte die Küche zum Flaschenhals werden, sich also mehr und mehr Zettel an der Pinnwand anhäufen.
Hier sehen wir zwei weitere vorteilhafte Eigenschaften ereignisgesteuerter Architektur. Die entkoppelten Ereignisprozessoren lassen sich unabhängig voneinander skalieren, um einer veränderlichen Last gerecht zu werden. Und durch das parallele Prozessieren der Ereignisse lassen sich hohe Leistungen erzielen.
Komplexe Fehlerbehandlung

Nun lassen sich Fehler nie ganz ausschließen. Fehlerquellen und Techniken diesen zu begegnen, gibt es viele. Betrachten wir ein Beispiel in der Pizzeria. Der Kellner hat eine schwer leserliche Handschrift, sodass der Koch hin und wieder eine Pizza backt, die nicht ganz der Bestellung des Kunden entspricht. Um dieses fehlerhafte Ereignis zu beheben, soll der Bote diese prüfen und bei Fehlern nicht ausliefern, sondern einen Korrekturauftrag in die Küche legen. Darauf reagiert der Koch und erstellt eine korrekte Bestellung wie üblich. Der Fehler wird auf diese Weise ereignisgesteuert aus dem System getilgt.
Die Fehlerbehandlung war hier durch eine kleine Erweiterung einfach umsetzbar. In größeren Systemen mit vielen Ereignisprozessoren und Abläufen kann sie jedoch recht komplex werden. Komplexer als bei anderen Architekturstilen. Mitunter müssen viele Ereignisse ausgelöst werden, um Fehler zu beheben und falsch geänderte Zustände rückgängig zu machen. Das ist ein Nachteil dieses Architekturstils, der mit dem Vorteil zusammenhängt, dass sich ereignisgesteuerte Architekturen durch Hinzufügen weiterer Ereignisprozessoren leicht erweitern lassen. Dadurch besteht ein Hang zur Komplexität, die umso größer wird, je mehr Kopplung es unter den Ereignisprozessoren gibt. Oben wurde beschrieben, dass die Prozessoren in der Verarbeitung der Ereignisse zeitlich voneinander entkoppelt sind. Inhaltlich gibt es aber durchaus Kopplungen. Ein Prozessor hängt von jenen ab, deren Ereignisse er konsumiert.
Domänen und Mediatoren
Um die Komplexität zu mildern, kann man versuchen inhaltlich abtrennbare Domänen in getrennten Abläufen zu implementieren deren Ereignisse von den Ereignisprozessor der anderen Domänen nicht benutzt werden. Dann sind immerhin die Ereignisprozessoren unterschiedlicher Domänen entkoppelt. Das verringert die Komplexität auf Kosten der Erweiterbarkeit. Kontrollieren lassen sich die inhaltlich getrennten Abläufe über sogenannte Mediatoren. Das sind Ereignisprozessoren, welche die am Ablauf beteiligten Ereignisprozessoren koordinieren. Im Gegensatz zu den gewöhnlichen Ereignisprozessoren kennt ein Mediator den Ablauf seiner Domäne, kann Ereignisse darin kontrollieren und korrigierend eingreifen. Das vereinfacht die Fehlerbehandlung, kostet aber Leistung, da die Koordination zusätzlich Zeit braucht. Außerdem kann der Mediator bei Lastspitzen zu einer kurzzeitigen Engstelle für den ganzen Ablauf werden, bis er entsprechend skaliert wurde.

In der Pizzeria bietet sich der Kellner für die Rolle des Mediators an. Dazu könnte man den Ablauf so ändern, dass der Koch die fertigen Pizzen in der Küche ansammelt, wo sie der Kellner abholt und verpackt zum Warenausgang bringt. Das bietet folgende Vorteile. Der Kellner kann die Pizzen selbst prüfen und die Fehler behandeln, die durch seine Handschrift mitverursacht wurden. Außerdem kann er beeinflussen, wann welche Bestellungen von den Boten ausgeliefert werden. Da er alle Bestellungen kennt, könnte er Gruppen von Bestellungen zusammenstellen, die in eine ähnliche Gegend geliefert werden müssen, um die Auslieferrouten der Boten zu optimieren. Und sollte einmal für das Ausliefern eine Fehlerbehandlung nötig werden, könnte er auch diese übernehmen. Bei dieser Variante bieten sich also mehr Möglichkeiten, den Ablauf zu steuern. Zum Nachteil wird die zentrale Kontrollinstanz des Kellners, wenn er pausieren will oder überlastet ist. In diesem Fall würde sich das Ausliefern bereits erfasster Pizzabestellungen verzögern. Diesen Nachteil gab es im Ablauf ohne Mediator nicht.
Zusammenfassung
Damit endet diese Einführung in die ereignisgesteuerte Architektur. Wir haben am softwarefernen Beispiel der Pizzeria Parallelen zu ereignisgesteuerter Architektur und die grundlegenden Eigenschaften dieses Architekturstils betrachtet. Neben einigen Vorteilen wie Leistung, Skalierbarkeit, Resilienz gegenüber Lastspitzen und Erweiterbarkeit wurden auch Herausforderungen wie erhöhte Komplexität sowie schwierige Fehlerbehandlung erklärt. Diese Vor- und Nachteile gilt es, den eigenen Anforderungen entsprechend abzuwägen, um eine für die jeweilige Anwendung passende Architektur zu finden. Bei einer Anwendung mit vielen Datenquellen, die regelmäßig Ereignisse produzieren, lohnt es sich eine ereignisgesteuerte Architektur in die Auswahl miteinzubeziehen.
Wenn Sie mehr über die Verarbeitung großer Datenmengen oder die Implementierung ereignisgesteuerte Architekturen erfahren möchten, besuchen Sie gerne unsere Seite IoT Analytics!
Quelle
[1] Mark Richards, Neal Ford (2020): Fundamentals of Software Architecture, O’Reilly Media
Tags
#Architektur, #Broker, #Ereignisgesteuert, #Ereignisse, #Eventbasiert, #EventBridge, #Events, #IoT, #Kafka, #Lambda, #MQTT, #MSK