Eventgetriebene Integrationsarchitekturen

Klassische, gewachsene Architekturen zeichnen sich durch eine Vielzahl an Architekturstilen aus. Die Kernsysteme folgen einer Schichtenarchitektur, wie sie in den 2000er Jahren angewendet wurde. Später entstandene Teile folgen einer SOAP-Architektur und noch später entstandene Teile folgen der Idee der Microservices. Teilweise werden noch Teile in der eigenen IT betrieben, andere Teile sind in einer Cloud verfügbar.
Um diese Architekturen zu vereinheitlichen und besser pflegbar und änderbar zu machen, müssen sie entsprechend der Ideen des Domain-driven Designs entlang der Geschäftsfunktionen vertikalisiert werden. Diese Vertikalisierung kann Schritt für Schritt eingeführt werden. Der Beitrag stellt ein entsprechendes Vorgehen und die zugehörigen Architekturmuster vor.
Idealisierte Architekturen
Von Schichtenarchitekturen zum heutigen Ideal
In den 90er Jahren und in den frühen 2000er Jahren wurden Software-Architekturen in Schichten entworfen. Diese Schichten sollten entkoppelt sein, um eine möglichst unabhängige Entwicklung der einzelnen Schichten zu ermöglichen. Typisch war eine sogenannte Dreischichtarchitektur mit Benutzer-Interface, Business-Logik und Persistenzschicht, die normalerweise in Form einer Datenbank implementiert wurde [1].
Aber immer mehr Anforderungen und die zunehmende Komplexität verlangten, dass weitere Schichten, z. B. eine allgemeine Datenzugriffsschicht, die die Datenzugriffe auf die Datenbank regelt, eingezogen wurden. Unterstützende Services wie Dokumenteingangs- und -ausgangsservices wurden eingefügt und über die Datenobjekte der Datenbank gesteuert. So entstanden Multi-Tier- oder auch Mehrschichten-Architekturen. Ein Beispiel für eine solche Architektur zeigt Abb. 1.
In der Abbildung werden sogenannte TAA als Benutzerinterface implementiert. Diese Strecken sind Tarifierungs-Angebot-Antrags-Strecken. In ihnen können Benutzer einzelne Versicherungsbausteine konfigurieren und abschließen – z. B. eine Hausrat- oder Kfz-Versicherung. Ebenso gehören Schadenbearbeitung und Kundenbetreuung zu den Benutzerschnittstellen. Diese Frontend-Anwendungen greifen über die Allgemeine Zugriffsschicht auf das Bestandssystem zu.
Mehrschichtenarchitekturen entkoppeln zwar die einzelnen Schichten, aber bei Hinzufügen neuer Funktionalitäten oder auch der Änderung von vorhandenen werden Änderungen in allen Schichten benötigt. Dies bedeutet für vorhandene Geschäftsfunktionen oder die Entwicklung neuer Geschäftsfähigkeiten viel Aufwand. Selbst wenn Änderungen schnell zu implementieren sind, müssen doch alle Geschäftsfunktionen in den geänderten Schichten getestet werden, was aufwändig und zeitintensiv ist.
Mehr noch: Partner, z. B. Bankpartner, die Lebensversicherungen verkaufen oder Reparaturbetriebe, die Schaden beheben, können nicht 1:1 auf die notwendigen Geschäftsfunktionen zugreifen. Jede Partnerintegration muss separat implementiert werden. Durch diese Nachteile bedingt haben sich seit 2010 serviceorientierte Ansätze mehr und mehr durchgesetzt – von den Enterprise-Services angefangen bis hin zu Microservice-Architekturen.
Ideale Architektur
Aus heutiger Sicht bilden ideale Architekturen Geschäftsfunktionen innerhalb von Domänenservices ab.
Domänenservice
Ein Domänenservice umfasst innerhalb eines Bounded-Kontexts [3] das Frontend, die Businesslogik und die Persistenzschicht. Als Bounded-Kontext wird hier eine Business-Funktion verstanden, die durch ein Two-Pizza-Team [2] implementiert werden kann. Der Begriff Domänenservice deutet an, dass sie nicht zwangsweise klein sein müssen, sondern vielmehr ihre Größe den Geschäftsanforderungen der jeweiligen Anwendung angepasst sind.
Ein Beispiel für eine solche Architektur zeigt Abb. 2. Die Tarifierungsservices umfassen nun nicht mehr nur die Benutzerschnittstelle, sondern auch die zugehörige Geschäftslogik und die Persistenzschicht. Diese Services greifen auf einen separaten Bezahlservice zu. Die Schadenbearbeitung wird in mehrere Geschäftsfunktionen und damit Services aufgeteilt – hier im Beispiel sind es Schadenmeldung und Schadenbearbeitung. Auch der Bereich Kundenbetreuung enthält mehrere Services: Adressen-, Vertrags- und Kundenänderung.
Innerhalb eines Domänenservices kommunizieren die einzelnen Schichten via synchroner (REST-)Schnittstellen. Dies ist insbesondere zwischen Frontend und Geschäftslogik gültig. Für externe Partner können die Geschäftsfunktionen ebenso als REST-API angeboten werden. Diese Form der Kommunikation ist auch als Nord-Süd-Kommunikation bekannt.
Nord-Süd-Kommunikation
Als Nord-Süd-Kommunikation wird Kommunikation bezeichnet, die über verschiedenen Layer hinweg Daten austauscht. Z. B. werden Daten zwischen einem Frontend und eigener Businesslogik auf der Basis einer Benutzerinteraktion ausgetauscht. Ziel moderner IT-Architekturen ist es, dass Nord-Süd-Kommunikation nur innerhalb eines Domänenservices stattfindet.
West-Ost-Kommunikation
Als West-Ost-Kommunikation wird Kommunikation zwischen einzelnen Domänenservices bezeichnet. Diese Kommunikation findet in der Regel ohne Interaktion von menschlichen Benutzern statt. Ein Beispiel ist der Datenaustausch zwischen einer Kundenverwaltung und einem Accountsystem bei der Adressänderung eines Kunden.
Die West-Ost-Kommunikation zwischen den einzelnen Geschäftsservices erfolgt asynchron via Events. Die Benutzung von Events ermöglicht eine hohe Entkopplung der einzelnen Dienste, da sie asynchron kommunizieren. Ein Service sendet die Information über ein neu erstelltes oder geändertes Geschäftsobjekt via Eventbus. Ein anderer Service, der diese spezifische Information abonniert hat, liest die Information aktiv vom Eventbus. Dadurch muss der Empfänger der Information nicht unbedingt verfügbar sein, wenn die Information entsteht, und die Services sind stark entkoppelt.
Heutige Architekturen
Heutige Architekturen sind stark gewachsen und weisen Architekturkonzepte aus unterschiedlichen Perioden auf. Abb. 3 zeigt ein Beispiel für eine solche gewachsene Architektur.
Viele Frontend-Anwendungen – z. B. die Tarifierungsstrecken von Versicherungen – greifen über eine allgemeine Zugriffsschicht auf einen Monolithen zu. Nicht selten läuft dieser Monolith noch auf Mainframe-Systemen und greift auf eine zentrale Datenbank zu. Neben dieser zentralen Zugriffsschicht existieren etwas modernere Systeme mit eigener Datenbank, die schon im Sinne von Domänen- und Microservices entwickelt wurden, aber nach wie vor noch über die allgemeine Zugriffsschicht auf die zentrale Geschäftslogik des Monolithen und auf die Datenbank mit ihrem zentralen Datenmodell zugreifen. Es gibt selbstenthaltene Services, die nur über Events und REST-Services arbeiten und klassische Services, die sich nach wie vor des Schichtenmodells bedienen.
Eine solche Architektur zeigt die technische Entwicklung der letzten Jahrzehnte. Aber viele Services sind nach wie vor über das zentrale Datenmodell der Datenbank gekoppelt. Eine Entkopplung kann nur gelingen, wenn die einzelnen Services über ihr eigenes Datenmodell verfügen und via Events die anderen Services über Änderungen informieren.
Auf dem Weg zum Ideal
Da wir in der heutigen Welt nicht mal zwei Jahre Services implementieren können, um eine ideale Architektur aufzubauen, müssen wir Schritt-für-Schritt-Verfahren entwickeln, um uns dem Ideal anzunähern. Dabei helfen uns Integrationsarchitekturen, die es erlauben, einzelne Teile einer gewachsenen Architektur zu ändern, ohne gleichzeitig andere Teile im gleichen Maße überarbeiten zu müssen.
Definition Integrationsarchitekturen
Eine Integrationsarchitektur ermöglicht unterschiedlichen Anwendungen einer IT-Landschaft, Daten auszutauschen. So können einzelne Dienste unabhängig in ihren Domänen arbeiten und trotzdem ganzheitlich über Domänen hinweg Geschäftsvorfälle abbilden. Einzelne Dienste innerhalb einer Domäne benötigen Integrationsarchitekturen, um sich an unterschiedliche Gegebenheiten einer schon vorhandenen IT-Landschaft anzupassen. Sie müssen z. B. auf Legacy-Datenbanken zugreifen können.
Integrationspattern
Um von den heutigen gewachsenen Architekturen auf dem Weg zum Ideal vorwärts zu kommen, muss man die einzelnen Services verbinden. Diese Integrationen können über unterschiedliche Integrationsmuster (Integration Pattern) gelingen. Abb. 4 zeigt solche typischen Muster.
Integration via synchroner REST-Calls
Ein Domänenservice bietet neben der typischen Benutzerschnittstelle auch den Service via REST an. In Abb. 4 ist dies ein Änderungsservice für Kundendaten. Dieser kann anhand einer Kunden-Identifikationsnummer aufgerufen werden und die zu ändernden Daten übergeben. Die REST-Schnittstelle wird idealerweise sowohl von externen Diensten, z. B. bei Partnerintegrationen, als auch von der Benutzerschnittstelle des Dienstes selbst genutzt.
REST-APIs
Heutige synchrone Schnittstellen werden in der Regel als REST-APIs implementiert. Dabei steht REST für Representational State Transfer[4]. Es beschreibt einen Architekturstil, in dem die Methoden des HTTP-Protokolls als vordefinierte Methoden für die Kommunikation benutzt werden. Da das HTTP-Protokoll das führende Protokoll des World Wide Web ist, gelingen so Schnittstellen, die über viele Partner hinweg verstanden werden und gut benutzbar sind. Der Name leitet sich vom Grundprinzip ab, dass Daten dann übertragen werden, wenn sich der Status in einer Anwendung ändert.
Integration via Events
Ein Domänenservice veröffentlicht Änderungen an seinem Datenobjekt auf einem Eventbus. Z. B. kann ein Service für Kundendatenänderungen ein Event erzeugen, das andere Services über die Änderung einer Kundenadresse informiert. Diese Änderung wird vom Vertragsänderungsservice aufgenommen, da der Dienst den Event "Adresse geändert" vorher abonniert hat. Verträge, die dem Kunden gehören, können nun entsprechend den geltenden Geschäftsregeln geändert werden.
Der abonnierende Service "Vertragsdatenänderung" liest aktiv den Event vom Eventbus. Das heißt aber auch, der Eventbus speichert den Event und der Service muss nicht laufen, wenn der Event erzeugt wird. Dieses Prinzip für eventgetriebene Architekturen führt zu einer hohen Entkopplung. Daher ist es für eine Service-zu-Service-Kommunikation zu empfehlen.
Definition eventgetriebene Architekturen
Eventgetriebene Architekturen sind IT-Architekturen, in denen einzelne Dienste (Services) ihre Daten über sogenannte Events austauschen [5]. Dabei sind Events Informationen über erzeugte oder geänderte Datenobjekte. Events können hierbei gesamthafte Datenobjekte, aber auch nur Teile dieser enthalten. In der Regel werden Events für die West-Ost-Kommunikation von Services eingesetzt.
Integration via UI-Komponenten
Es kann auch vorkommen, dass Benutzerschnittstellen eingebunden werden, da ihre Implementierung unverhältnismäßig hohe Aufwände in einer fremden Domäne erfordert. Die Benutzerschnittstellen können z. B. über Web-Komponenten technisch eingebunden werden. Typische Beispiele hierfür sind Dokumenten-Up- und -Download. Wiederaufsetzverfahren und Prüfung zulässiger Datenformate kann aufwändig und zeitraubend sein. Daher ist es besser, diese nicht nur technisch z. B. via REST-Calls einzubinden, sondern vielmehr via UI-Komponente.
Diese Komponenten bilden dann die gesamte Funktionalität für den Benutzer ab. Der aufrufende Service wird anschließend z. B. via Event darüber informiert, dass der Vorgang abgeschlossen ist. Unter anderem können Tarifierungsbausteine einen Dokumenten-Upload aufrufen, um dem Benutzer die Möglichkeit zu geben, Nachweise und Unterlagen in das System zu laden.
Web Components
Web Components sind eine Sammlung von Funktionen, die ein Standard-Komponentenmodell für Web-Anwendungen bereitstellen und damit Kapselung und Interoperabilität von individuellen HTML-Elementen erreichen [6]. Durch sie kann man auf einer Web-Oberfläche auch Anwendungen aus unterschiedlichen Frameworks wie Angular oder React für den Benutzer darstellen [7].
Auflösung der Schichten – Vertikalisierung
Um die Schichten einer klassischen Architektur aufzulösen, muss man die Domänenservices finden, die im jeweiligen Fall eine Geschäftsfunktion abtrennbar abbilden und gleichzeitig innerhalb eines Bounded-Kontexts implementierbar sind. Im Beispiel in Abb. 5 wurden die Services Tarifierung, Vertragsübersicht, Kundenübersicht und weitere als solche vertikalen Geschäftsfunktionen identifiziert.
Diese Funktionen können unabhängig voneinander implementiert werden. Sie stellen ihre Funktionalität via Komponente zur Verfügung. Damit sie parallel darstellbar sind, wird ihre Komposition in der Oberfläche über eine App Shell orchestriert [8]. Alle diese Services benötigen nach wie vor Zugriff auf klassische Dienste – z. B. auf Verträge aus dem Bestandssystem. Auf diese greifen die vertikalisierten Services via domänen-spezifischer Adapter zu. Um eine Entkopplung zu gewährleisten, werden die Dienste nur via Eventbus aus Adaptern heraus angesprochen. Die bereits vertikalisierten Dienste kommunizieren untereinander via Events und stellen die Geschäftsfunktion sowohl als UI-Komponente als auch via REST-API zur Verfügung. Diese Zielarchitekturen müssen Schritt für Schritt erreicht werden. Diese Schritte werden im Folgenden beschrieben.
Vorgehensmodell
Ausgangssituation
Das Beispiel in Abb. 6 auf der linken Seite zeigt eine klassische Schichtenarchitektur. Die Frontend-Dienste "Tarifierung", "Vertrags-Übersicht" und "Kunden-Übersicht" greifen über eine allgemeine Zugriffsschicht auf das klassische Bestandssystem zu. Diese Frontendservices sind zwar schon entlang von Geschäftsfunktionen geschnitten, enthalten aber übergreifende Funktionalitäten, die teilweise doppelt implementiert wurden oder aber auch gemeinsam genutzt werden. Dadurch entsteht eine enge Kopplung nicht nur über die allgemeine Zugriffsschicht, sondern auch in den Frontend-Diensten selbst.
Unterstützende Dienste wie Dokumentenausgang, Dokumenteneingang und Archiv werden rein durch das Bestandssystem gesteuert.
Schritt 1 – Events
Abb. 6 zeigt den ersten Schritt einer Vertikalisierung, indem eine zusätzliche Schicht in die klassische Schichtenarchitektur eingezogen wird. Dies klingt nach einem Widerspruch – allerdings ist die zusätzliche Schicht ein Eventbus, der es ermöglicht, das Bestandssystem von den unterstützenden Diensten und den Frontend-Diensten zu entkoppeln. Diese Entkopplung erlaubt es, alle weiteren Schritte unabhängig und von Release-Zyklen im Bestandssystem und den Diensten durchzuführen. Man erreicht, dass die einzelnen Teams ihre Modernisierung unabhängig von anderen Teams durchführen können.
Schritt 2 – Domänenschichten
Abb. 7 zeigt den zweiten Schritt einer Vertikalisierung. Die vorher vorhandene Allgemeine Zugriffsschicht wird durch domänengetriebene Schichten ersetzt. Diese Domänenschichten bieten schon APIs für ihre Domänenressourcen an. Dieser Schritt kann auch schon genutzt werden, um Überlappungen in den Frontend-Diensten zu entkoppeln und den jeweiligen Zugriff über die Domänenschichten abzubilden. Die Domänendienste bekommen jetzt schon einen eigenen Persistenz-Layer, der es erlaubt, die Data Ownership (Dateneigentum) aus dem Bestandssystem auf die Domänendienste zu übertragen. Dadurch wird eine weitere Entkopplung erreicht.
Die Data Ownership muss sorgfältig gewählt werden. Zu empfehlen sind Workshop-Formate, die eine gute Auswahl und einen guten Schnitt der Domänendienste erlauben.
Data Ownership
Data Ownership ist eine der grundlegenden Ideen des Domain-driven Designs (DDD). Dabei ist es nur dem Eigentümer eines Business-Objektes erlaubt, seine Daten zu ändern. Im DDD wird hierfür der Begriff des Root Aggregates eingeführt. Für die Entwicklung von vertikalisierten Architekturen ist es wichtig, dieses Root Aggregate sinnvoll zu bestimmen. Methodiken wie Domain Storytelling und Event Storming bilden eine gute Grundlage für diese Aufgabe [9].
Schritt 3 – Vertikalisierung
Im dritten Schritt werden die bisherigen Domänendienste durch ihre APIs erweitert, so dass auch sie ihre Geschäftsfunktion per API und Benutzerschnittstelle zur Verfügung stellen. Die vorher implementierten Domänenschichten bleiben erhalten und dienen zur Service-zu-Service-Kommunikation via Events. Die unterstützenden Dienste wie Dokumenteneingang und -ausgang oder das Archiv bleiben erhalten.
Auch Teile des Bestandssystems bleiben erhalten. Auf diese kann via Events zugegriffen werden.
Die vorgestellten Schritte können für bestimmte Domänen – wie z. B. die Verkaufsdomäne – durchgeführt werden. Sie müssen nicht für alle Bereiche gleichzeitig durchgeführt werden. Dadurch erreicht man ein iteratives Vorgehen, bei dem die gesamte Organisation lernen und sich anpassen kann.
Zusammenfassung
Klassische Schichtenarchitekturen können über Integrationsarchitekturen Schritt für Schritt in moderne vertikale Architekturen umgebaut werden. Dabei werden die Domänendienste der Außenwelt per API zur Verfügung gestellt (Nord-Süd-Kommunikation). Die Dienste selbst kommunizieren via Events (West-Ost-Kommunikation). Auch verbliebene Teile der klassischen Architektur kommunizieren via Events, um eine möglichst hohe Entkopplung zu erreichen.
Diese Entkopplung ermöglicht auch ein Schritt-für-Schritt-Vorgehen, das für einen Umbau gewachsener Architekturen zwingend notwendig ist, um in einer modernen Welt schnell und zuverlässig auf sich ändernde Marktbedingungen und Technologien reagieren zu können.
- Wikipedia: Schichtenarchitektur
- AWS: Two-Pizza Team
- E. Evans: Domain Driven Design – Tackling Complexity in the Heart of Software, Addison Wesley 2003
- Wikipedia: Representational State Transfer
- Dr. A. Junker: Microservices: Event-getriebene Integrationsarchitekturen im Einsatz
- Wikipedia: Web Components
- Angular | React
- Dr. A. Junker: Microfrontends in der Praxis
- Hofer, S., Schwentner, H.: Domain Storytelling: A Collaborative, Visual, and Agile Way to Build Domain-Driven Software, Pearson, 2021
A. Brandolini: EventStorming
HJ
am 22.02.2024Kenn' ich nicht ;-)
Es gibt Aggregates und die haben eine Root Entity...
Ansonsten ein sehr gelungener Artikel und eine gute Übersicht, danke dafür!
Annegret Junker
am 22.02.2024in der Zwischenzeit ja - und im täglichen Leben benutze ich es auchs o.
Eric Evans benutzt "Aggregate Root" (was ich auch als ein wenig doppelt-gemoppelt empfinde). Vgl. Evans, E. Domain-Driven Design, S. 127/128 :-D
Liebe Grüße Annegret