Monolithische Systeme in Microservices und Self-Contained Systems zerlegen

Wie schaffen wir es komplexe monolithische Systeme so zu zerlegen, dass die einzelnen Sub-Systeme leicht und unabhängig voneinander entwickelt werden können? In diesem Erfahrungsbericht zeige ich, wie wir ein Shopping-Portal in Microservices und Self-Contained-Systems (SCS) zerlegt haben.

Der Artikel ist ein Erfahrungsbericht und beschreibt nur unseren Weg. Es gibt hier sicherlich viele alternative Lösungen. Mit Sicherheit gibt es auch rein Microservice-basierte Architekturen, welche die hier gezeigten Probleme lösen könnten.

Video-Präsentation zum Artikel

Ausgangssituation: der Monolith

Monolithen werden in der IT häufig als einzelnes System beschrieben, das alle Funktion beinhaltet, siehe https://www.itwissen.info/Monolithische-Software-Architektur.html. Nach dieser Definition hätten wir eigentlich kein Problem, da ich hier ein Content-basiertes Shopping-System betrachte, welches diverse Backend-Systeme über REST-APIs aufruft, die Teile der Funktionalität bereitstellen, z.B. einen Einkaufwagen-Service (ShoppingCart).

Bei genauerem Hinschauen finden wir im Content-basierten Shopping-System aber unter anderem folgende Funktionalitäten:
  • Content Management System zum Präsentieren und Verwalten von redaktionellen Inhalten
  • Produkt Katalog zum Pflegen der zu verkaufenden Produkte
  • Shopping System zum Verkaufen der Produkte, inkl. Bezahl-Funktionen
Diese Funktionalitäten könnte man noch weiter aufsplitten, es sollte aber jetzt schon klar sein, dass hier mehrere Funktionen in einem System zusammengefasst sind. Das folgende Schaubild zeigt diverse Komponenten innerhalb des Monoliths, die später in Microservices oder SCS verschoben werden.

Monolith

Ein weiteres Kriterium für ein monolithisches System ist die Größe der Codebase. Der Code war zwar in einzelne Maven-Module strukturiert, da es aber über 200 waren, spreche ich von einer großen Codebase.

Probleme

Da sich alle Maven-Module in einem Git-Repository befunden haben, war das Einrichten der Entwicklungsumgebung ziemlich komplex. Der Maven-Build-Prozess zum Generieren, Kompilieren usw. lief bei mir mehrere Minuten, Kollegen mit älteren Notebooks brauchten teilweise fast eine Stunde. In der späteren Entwicklung muss man natürlich nicht immer alle Maven-Module bauen, so dass der lange Build eine "einmalige Sache" ist. In der Praxis baut man dann aber doch häufig lieber alles, um sich bei Änderungen wirklich sicher zu sein.

Neben dem Zeitverlust ist die Komplexität, die mit der Größe des Systems kommt, ein weiterer Nachteil, da Komplexität eine klassische Fehlerquelle ist.

Größe und Komplexität haben dazu geführt, dass neue Entwickler ein relativ langes Onboarding benötigt haben, um produktiv am System mitwirken zu können. Durch die Verwendung von Microservices hätte dieses Problem entschärft werden sollen, aber...

Microservices haben nicht gereicht

Ein Microservices ist ein möglichst kleiner Service, der sich auf eine Aufgabe konzentriert. Typischerweise bietet der Microservice eine REST-API an, um von anderen Systemen benutzt zu werden. Eine ausführliche Beschreibung von Microservices findet ihr hier: https://www.martinfowler.com/microservices/

In meiner Firma werden immer mehr Microservices angeboten, die eine API aus der Liste der tmforum Open APIs implementieren. Diese APIs bieten alle typischer Weise benötigten Funktionalitäten an, so dass man sie in einem Frontend bzw. einem Content-basierten Shopping-System nur noch richtig einbinden müsste. Dann hätten wir auch keinen Monolithen mehr, sondern eine Microservice- oder zumindest servicebasierte Architektur.

Leider haben wir bisher nur einen Teil der Open API Microservices implementiert, so dass der andere Teil im Content-basierten Shopping-System implementiert wurde. Das ist anfangs schneller, produziert aber mittel- und langfristig monolithische Strukturen mit den zuvor erwähnten Größen und Komplexitätsnachteilen.

Ein weiteres Problem war, dass die Microservices selbst noch in der Entwicklung waren. Ihre APIs wurden noch auf die speziellen Bedürfnisse unseres Unternehmens angepasst, so dass sie je nach Entwicklungsstand auch nicht gut zueinander gepasst haben. Dadurch waren Client-seitige Mappings zur Verbindung der APIs miteinander nötig. Das ist allerdings kein allgemeiner Nachteil von Microservices. Sobald ein Großteil der Microservices mit stabiler API vorhanden ist, sollte eine einfachere Integration der Normalfall sein.

Dennoch kann die Menge an benötigten Microservices zu einem Problem werden. Jede API Integration schafft etwas mehr Komplexität im Frontend-System, da neben dem eigentlich Integrations-Code (Client SDK) auch die Logik drumherum wächst. Während die Backend-Logik sich auf die einzelnen Microservices verteilt und so gut beherrschbar bleibt, konzentriert sich Frontend-Logik in einem System.

Im Microservice Schaubild ist die ShoppingCart-, Payment- und Produkt Katalog Verwaltungs-Funktionalität in Microservices ausgelagert. Der Monolith ist also etwas geschrumpft. Durch die zuvor erwähnten Probleme benötigt der Monolith aber immer noch Mapping-Komponenten, um die einzelnen Microservices richtig zu integrieren.

Monolith mit Microservices

Aufteilung der Funktionalität in Self-Contained Systems (SCS)

Mit Self-Contained Systems war es uns möglich große Funktionalitäts-Blöcke aus dem Monolithen herauszubrechen. Dadurch wurde der Monolith signifikant kleiner. Im folgenden Bild sehr ihr den verkleinerten Monolithen. Dort wurde ein Checkout SCS und ein ShoppingCart SCS aus dem Content-basierten Shopping-System rausgelöst.

Kleiner Monolith mit SCS und Microservices

Self-Contained System SCS

Unter einem SCS verstehen wir eine autonome Web-Anwendung, die alles Notwendige zur Umsetzung ihrer UseCases enthält. Der SCS kann somit von einem Team unabhängig von anderen Teams und Systemen entwickelt und deployt werden. Die Integration des Checkout SCS und des ShoppingCart SCS erfolgt über das UI, dazu leitet das Content-basierten Shopping-System den Benutzer auf die UI des jeweiligen SCS weiter. Wenn der Benutzer den Prozess bzw. den UseCase des SCS durchlaufen hat, wird er wieder zurück geleitet. Im einfachsten Fall funktioniert das mit normalen Links.

Weiterführende Informationen und eine ausführliche Definition von SCS findet ihr hier:

Migration zu SCS-Architektur

Grundsätzlich gibt es drei verschiedene Migrations-Strategien. Ihr könnt den SCS auf der grünen Wiese neu entwickeln, den vorhandenen Code aus dem Monolithen übernehmen oder einen Mittelweg finden.

Bei unserer Migration vom Monolithen zum SCS haben wir uns für die Übernahme vom vorhandenen Code entschieden, weil es im Monolithen Module gab, welche den Code der einzelnen Funktionalitäten gruppiert hatten. Außerdem haben wir den Technologie-Stack des Monolithen übernommen. Dieser Ansatz ermöglicht ein schnelles Herauslösen des SCS aus dem Monolithen mit relativ wenig neuen Fehlern. Der Nachteil ist aber, dass man die Chance zu einem besser, passenden Technologie-Stack zu wechseln verpasst.

Vorhandene Microservices werden damit auch im SCS verwendet. Die Microservice-Logik wird nicht in den SCS integriert. Damit ist das SCS entwickelnde Team zwar vom Anbieter des Microservices abhängig, andererseits wird aber auch keine Funktionalität dupliziert. Außerdem hat man im einzelnen SCS deutlich weniger Abhängigkeiten von anderen Services bzw. Teams als im Monolith, der alle Microservices integriert hatte.
 
Welchen Migrationsweg ihr geht, solltet ihr von Fall zu Fall entscheiden. Wenn die Code-Qualität im Ausgangssystem (Monolith) zu schlecht ist, ist eine Neuentwicklung der Logik innerhalb des SCS vermutlich die bessere Lösung.

Fazit bzw. Vorteile SCS

Die Einführung von SCS hat uns geholfen den Monolithen zu verkleinern und somit den Code bzw. die zu Implementierende Fachlichkeit auf mehrere Teams zu verteilen. Die Teams können nun unabhängig voneinander entwickeln und deployen - ein klassischer Vorteil von verteilten System im Vergleich zu Monolithen.

Durch die kleinere bzw. auf mehrere Systeme verteilte Codebase ist das Onboarding neuer Team-Mitglieder deutlich leichter und schneller. Sie müssen sich die Entwicklungsumgebung nur für den kleinen SCS einrichten und nicht für das komplette Content-basierte Shopping-System.

Grundsätzlich passt der vertikale Schnitt eines SCS entlang der Funktionalität auch sehr gut zur agilen Entwicklung. Bei der agilen Entwicklung möchte man, dass ein Team vor Kunde mit jedem Sprint Werte abliefern kann. Das ist beim SCS gegeben, da auch das UI zum SCS gehört. Wenn sich die Teams alternativ entlang der horizontalen Schichten (UI, Business Logik, Persistenz) aufteilen, ist das deutlich schwieriger, da die Werte beim Kunden meist über das UI ankommen.

Kommentare

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

Authentifizierung in Web-Anwendungen mit Spring Security 6

Validierung per Annotation in Java und Spring