Loading...

Wie wird man Microservices wieder los?

Architecture
23. Mai 2024
7 Minuten Lesezeit
Beitrag teilen:
Gefällt dir der Beitrag?
Du wirst den Newsletter lieben!

In den vergangenen Jahren haben etliche Unternehmen aus dem Mittelstand die viel gepriesenen Microservices ausprobiert und mussten feststellen, dass diese weniger leichtgewichtig sind als der Name vermuten lässt – Microservices im Mittelstand sind oft Overengineering . Jetzt müssen sie zurückgebaut und in eine pragmatischere Architektur überführt werden. Wir erklären, wie das gelingt.

Keine Angst: Der Rück- und Umbau der Architektur ist nicht nur aufwendig, sondern auch eine Chance. Denn in der Softwareentwicklung gibt es keine One-Size-Fits-All-Lösung; die passende Architektur für die eigene Software wird auf Basis der Anforderungen an die Software ausgewählt. Dafür werden die Anforderungen aller Stakeholder zusammengetragen. In vielen Projekten wird das nicht ausreichend gemacht. Bei einem Umbau der Architektur kann es nachgeholt und überprüft werden.

Auch kann auf Veränderungen seit Beginn des Projekts reagiert werden. Annahmen, die zu Projektbeginn noch valide waren, haben sich wahrscheinlich verändert, neue Anforderungen sind dazugekommen. Denkbar, dass nicht alle Entscheidungen von damals heute noch gelten.

Vorarbeit 1: Prüfe, welche Teile der Software verteilt bleiben müssen

Viele Anforderungen können durch Interviews mit den Stakeholdern herausgefunden werden. Wie hat sich die Topologie verändert, also die Aufteilung der Abteilung? Gibt es mehr als ein Team, das an dem Projekt arbeitet? Das kann ein Grund sein, auch weiterhin mit einem verteilten System zu arbeiten. Jedes Team ist für einen eigenen Service verantwortlich. Dadurch werden die Teams unabhängig und sind eigenverantwortlich.

Ein anderer, wenngleich seltenerer Grund für eine Aufteilung in mehrere Applikationen ist die Performance. Rechenintensive Operationen sollen nicht die Hauptapplikation stören. Eine Auslagerung in eine separate Anwendung ist dafür eine gute Lösung. Dies sind nur zwei Gründe für eine Aufteilung der Software in mehrere Applikationen.

Aus den Beispielen soll deutlich werden: Auch wenn aufgrund negativer Erfahrungen die Microservice-Architektur zurückgebaut wird, sei niemals dogmatisch. Evaluiere, welche Teile der Software weiterhin verteilt bleiben sollen.

Vorarbeit 2: Prüfe, was zusammengehört

Services, die unter veralteten Anforderungen oder falschen Annahmen aufgeteilt wurden, müssen in einer Applikation zusammengeführt werden. Zwischen diesen Services gibt es viel Kommunikation. Nahezu jede Anfrage muss durch mehrere Systeme. Daten aus mehreren Datenquellen müssen zusammengeführt werden, um Requests zu bearbeiten. Fehlende Transaktionssicherheit führt zu Inkonsistenzen. Und das System kommt bereits mit ein paar Dutzend Requests an die Belastungsgrenze.

Diese Systeme werden fälschlicherweise als Microservice-Architektur bezeichnet. Bei genauerer Betrachtung sind sie eher ein verteilter Monolith. Die Services im System sind nicht unabhängig voneinander, sie sind ein Bounded Context und hätten niemals aufgeteilt werden sollen. Was zu Beginn der Entwicklung simpel klang, zeigt sich im Livebetrieb als schwer veränderbares und fehleranfälliges System.

Doch wo fangen wir an beim Rückbau der Microservice-Architektur und beim Umbau zu einer passenderen Architektur?

Beim Umbau eines verteilten Systems auf eine monolithische Anwendung fangen wir da an, wo es die meisten Interaktionen gibt. Ein über Jahre gewachsenes System kann nicht in kurzer Zeit vollständig umgebaut werden. Daher ist es wichtig, von Anfang an zu priorisieren und dort zu beginnen, wo der größte Nutzen entsteht.

Aufgabe ist es herauszufinden, welche Services zuerst zusammengeführt werden müssen, um den größten Nutzen zu erzeugen.

Schritt 1: Ist-Zustand feststellen

Dazu nehmen wir den aktuellen Zustand auf. Das kann durch die Erarbeitung eines Architekturdiagramms mit dem Team geschehen. Darin werden alle Interaktionen zwischen den Services dargestellt. In arc42 ist das beispielsweise Kapitel 5: Building Block View ; im C4-Modell ist es das System-Context- und Container-Diagramm .

Der Service mit der höchsten Kupplung ist ein guter Kandidat für das erste Refactoring. In einem Architekturdiagramm wird der durch die Anzahl eingehender und ausgehender Pfeile identifiziert. Die Pfeile repräsentieren die Interaktionen zwischen den Services.

In vielen verteilten Monolithen ist ein zentraler Service zu finden, der mit fast allen anderen Services interagiert. Das ist häufig ein User- oder Tenant-Service. Ein solcher Service muss bei nahezu jeder Interaktion im System angefragt werden, oft auch mehrfach innerhalb der Verarbeitung eines einzelnen Requests.

Da dieser Service so zentral ist, ist er ein guter Kandidat, um die Grundlage für den künftigen Monolithen zu bilden.

Der erste Service, den wir in den Monolithen zurückführen, sollte aktiv Probleme in der Entwicklung liefern. Beispielsweise einer, der durch die regelmäßigen API-Zugriffe auf den User-Service an die Lastgrenze stößt. Oder ein Service, der häufig bei neuen Features angepasst wird. Suche nach einem Service, der durch den Umbau direkten Einfluss auf die Entwicklung oder die KPI des Produkts hat.

Schritt 2: Baue einen Modulithen

Der Begriff Monolith ist heutzutage negativ konnotiert. Dazu führten Erfahrungen der letzten Jahrzehnte mit einer großen einzelnen Anwendung. Die Anwendungen waren schwergewichtig, unflexibel und hatten viele Seiteneffekte.

Etabliert wurde der Begriff Modulith, um der negativen Konnotation zu entkommen. Damit wird verdeutlicht, dass zwar viele Programmteile in einer Applikation gebündelt sind, aber durch Module voneinander getrennt werden.

In den vergangenen Jahrzehnten wurden verschiedene Strategien zur Modularisierung erfunden und favorisiert. In Java kann seit Java 9 mit Modulen gearbeitet werden. Auch die schon immer vorhandenen Packages sind zur Modularisierung des Codes gedacht. Beide Ansätze haben jedoch Nachteile, deshalb erwiesen sie sich für viele Projekte nicht als hinreichende Lösung.

Archunit oder das neuere Spring-Modulith-Projekt nutzen Annotationen, um Architektur-Constraints zu beschreiben. Tests stellen in der CI sicher, dass diese nicht verletzt werden.

Entwickler wollen klare Regeln. Der Compiler soll Zugriffe verhindern, die die Architektur verletzen. In einer Layer-Architektur darf nur der Business-Layer auf die Persistenzschicht zugreifen. Aus dem Wunsch nach klaren Regeln erscheint die Microservice-Architektur attraktiv, sie stellt die konsequenteste Abgrenzung von Modulen dar.

Der Wunsch, die Architektur so stark es geht zu forcieren, kommt aus einer Erfahrung, die fast jeder Entwickler gemacht hat: dem Big Ball of Mud, dem gefürchteten Spaghetticode. Darum braucht auch der Monolith klare Regeln und Grenzen.

Eine Alternative zur Modularisierung ist die Trennung des Sourcecodes durch das Buildtool. In Maven oder Gradle kann ein Projekt in mehrere Unterprojekte aufgeteilt werden. Diese Unterprojekte repräsentieren ein Modul der Applikation. Eine Abhängigkeit zwischen den Modulen wird als Dependency deklariert. Damit können unabsichtlich eingeführte Kupplungen zwischen zwei Modulen verhindert werden. Der Compiler wird die Verletzung der Architektur nicht zulassen.

Es gibt zwei grundlegende Ansätze, Module zu schneiden: nach der Funktion oder nach der Domäne. Die klassische Layer-Architektur ist ein Schnitt nach den Funktionen. Es werden Klassen mit einer ähnlichen Rolle zusammengefasst. Beispielsweise liegen alle Klassen, die auf die Datenbank zugreifen, in der Persistenzschicht.

Vorteile dieser Architektur sind die geringen Kosten und das einfache Verständnis. Den Entwicklern fällt es nicht schwer zu entscheiden, in welchen Layer die aktuell bearbeitete Klasse gehört. Außerdem lässt sich die Architektur einfach durch Regeln testen und absichern.

Doch je größer der Code wird, desto mehr Probleme entstehen mit der Layer-Architektur. Es wird schwieriger, die Klassen zu identifizieren, die zu einem Feature gehören. Dadurch wird es wahrscheinlicher, Seiteneffekte zu produzieren, und die Entwicklung verzögert sich. Auch aus dieser Erfahrung heraus wurden Microservices und Domain-Driven-Design (DDD) in den letzten Jahren populär.

Wenn wir unsere Module mit dem Bounded Context modellieren wie in DDD beschrieben, können wir die Applikation noch zu einem späteren Zeitpunkt aufteilen. Damit erhalten wir uns die Flexibilität, die Anwendung an die künftigen Gegebenheiten der Organisation anzupassen.

Zusätzlich erhöhen wir die Wartbarkeit der Applikation. Im Gegensatz zum Schnitt nach der Funktion liegen in den nach DDD modellierten Modulen alle notwendigen Klassen nebeneinander.

Die Ansätze, den Modulith nach DDD und nach Layern zu trennen, kann auch kombiniert werden. In diesem Szenario werden die Module mittels DDD modelliert. Innerhalb der Module wird dann die Layer-Architektur genutzt, um den Fluss der Daten sicherzustellen.

Sämtliche Techniken, die wir diskutiert haben, sind kombinierbar.

Ich empfehle eine Aufteilung der Module mit dem Buildtool. Dadurch erhalte ich keine Vorschläge in der Autocompletion meiner IDE von Klassen, die ich nicht verwenden darf. Innerhalb eines Moduls kann der Datenfluss durch ein Tool wie Archunit oder Spring Modulith modelliert werden. Das ermöglicht schnelle und unkomplizierte Anpassungen, wenn beispielsweise neue Layer innerhalb des Moduls hinzugefügt werden.

TL;DR

Jedes Projekt ist anders. Doch die Erfahrung der vergangenen zehn Jahre hat gelehrt, dass Microservice-Architekturen keine Lösung für alle Probleme sind. Vor allem mittelständische Unternehmen haben selten die Notwendigkeit und die Mittel, eine derart komplexe Architektur zu stemmen. Gerade die Unternehmen sollten auf leichtgewichtigere Architekturen setzen. Der Modulith ist eine adäquate Alternative.

Beim Umbau auf einen Modulithen starten wir mit den Services, die die meisten Interaktionen im System haben. Dann stellen wir mit einer der vorgestellten Techniken sicher, dass ein fehlerhafter Zugriff zwischen den Modulen nicht möglich ist.

Dabei sollte uns immer bewusst sein: Jede Architektur hat Vor- und Nachteile. Daher sollten wir die Experten zusammenbringen, die diese Vor- und Nachteile diskutieren und auf dieser Basis die beste Entscheidung fällen können.

Kennst du schon Marcus' Backend Newsletter?

Neue Ideen. Jede Woche!
Top