12
.
October
2025
·
12
Minuten Lesezeit

Warum schlechte Monolithen entstehen und wie man sie vermeidet.

Über Jahre gewachsen, schwer verständlich und kaum änderbar – schlecht strukturierte Monolithen sind kein Zufall, sondern das Ergebnis typischer Muster in Organisation und Architektur. Viele Unternehmen stehen vor der Herausforderung, historisch gewachsene Systeme in moderne, wartbare Architekturen zu überführen. Über Jahre entstandene Abhängigkeiten und fehlende fachliche Struktur machen diesen Wandel oft schwierig. Doch schlecht aufgebaute Systeme sind kein Zufall – sie sind das Ergebnis unklarer Verantwortlichkeiten, technischer Kompromisse und fehlendem Verständnis für die Fachlichkeit. Um zu begreifen, warum Monolithen mit der Zeit unwartbar werden, lohnt sich ein Blick auf das Gegenteil: Was macht eigentlich ein gutes Modul aus? Erst wenn klar ist, wie eine saubere fachliche und technische Abgrenzung aussieht, wird sichtbar, wo gewachsene Systeme in die falsche Richtung abgebogen sind.

Was ist ein gutes Modul?

Bereits 1979 haben sich Edward Yourdon und Larry L.Constantine in ihrem Buch „Structured Design: Fundamentals of a Discipline ofComputer Program and Systems Design” (siehe [1]) Gedanken darüber gemacht, was ein gutes Modul ausmacht. Sie haben dabei zwei Begriffe etabliert, die bisheute ihre Gültigkeit nicht verloren haben und herangezogen werden, wenn es um Modularisierung geht: Kopplung und Kohäsion.

 Ein gutes Modul zeichnet sich demnach vor allem durch diese zwei zentralen Qualitätsmerkmale aus: geringe Kopplung und hohe Kohäsion. Diese beiden Prinzipien sind entscheidend für die Wartbarkeit, Erweiterbarkeit und Verständlichkeit von Softwaresystemen.

 Kohäsion beschreibt, wie stark die Bestandteile eines Moduls fachlich zusammengehören. Ein kohäsives Modul bündelt Funktionen, die einem gemeinsamen Zweck dienen oder auf denselben Teil der Domäne bezogen sind. Hohe Kohäsion bedeutet, dass die Elemente eines Moduls eng miteinander verwandt sind – sie arbeiten auf denselben Daten, folgen einem gemeinsamen Prozess oder erfüllen eine klar umrissene Aufgabe. Dadurch wird das Modul verständlicher und lässt sich in der Regel einfacher testen, warten und weiterentwickeln.

 Kopplung hingegen beschreibt die Abhängigkeiten eines Moduls zu anderen Modulen. Ein gutentkoppeltes Modul kennt möglichst wenig über andere Teile des Systems und interagiert mit ihnen über wohldefinierte Schnittstellen. Dabei geht es bei Kopplung nicht nur um die Anzahl der Abhängigkeiten zu anderen Modulen, sondern auch darum, welcher Art diese Abhängigkeiten sind. Wenn die Abhängigkeit z.B. darin besteht, dass ein Modul ein anderes aufrufen muss, um dringend benötigte Daten zu bekommen, so ist die Kopplung höher, als wenn ein Modul ein anderes mittel seines asynchronen Events über Änderungen informiert, bei dem es egal ist, wann das Event zugestellt wird oder sogar, ob es überhaupt zugestellt wird.

 Geringe Kopplung sorgt dafür, dass Änderungen in einem Modul nicht zwangsläufig Auswirkungen auf andere Module haben. Das reduziert Seiteneffekte und erhöht die Robustheit und Flexibilität der Software.

Ein gutes Modul ist also kohärentin sich und möglichst unabhängig von seiner Umgebung. Es lässt sich isoliert betrachten, verstehen und ersetzen. Das Zusammenspiel von hoher Kohäsion und niedriger Kopplung ist ein zentrales Ziel bei der Gestaltungmodularer Architekturen – ob in monolithischen Systemen oder in Microservices. Nur so lassen sich langfristig stabile und evolvierbare Softwaresysteme bauen.

 

Fehler aus der SOA-Zeit

Ende des letzten Jahrtausends und zu Beginn dieses Jahrtausends wurde daraus ein Architektur-Stil entwickelt, der sich Service-Orientierten Architekturen (SOA)nannte. Die Idee der Unabhängigkeit wurde dabei insofern auf die Spitze getrieben, dass Module auch unabhängig deployed werden sollten. Jeder Service hatte also eine eigene Laufzeitumgebung. Beim Design dieser Architekturen wurde allerdings aus meiner Sicht häufig eine Reihe von Fehlern gemacht, auf die ich im Folgenden näher eingehen will.

Die Entwicklung eines Services war teuer und so wurde ein starker Fokus auf die Wiederverwendbarkeit eines Services gelegt. Ein Service war demnach besonders wichtig und wertvoll und damit gut, wenn er besonders häufig wiederverwendet wurde, also, wenn er besonders vielen Konsumenten hatte.

Das hatte allerdings verschiedene Probleme zur Folge. Das erste, naheliegende Problem ist die hohe Kopplung. Wenn ein Service von vielen Konsumenten verwendet wird, liegt ein hohes Maß an Kopplung vor, was genau den oben beschriebenen Kriterien eines guten Moduls widerspricht. In der Praxis zeigte sich auch tatsächlich genau das angesprochene Problem. Das Ändern einer Schnittstelle führte häufig dazu, dass alle Konsumenten auch angepasst werden mussten, was einen immensen Aufwand bedeutete. Schnittstellen wurden daher nur sehr selten geändert und waren in service-orientierten Architekturen sehr starr, was die Weiterentwicklung sehr aufwendig werden ließ, weil man entweder alte Schnittstellenversionen weiter pflegen musste oder eben bei jeder Schnittstellenänderung die Releases aller Clients mit koordinieren musste.

Enterprise Service Busses (ESB) versuchten das Problem zu lösen, indem sie u.A. die Möglichkeit eines Mappings zwischen verschiedenen Versionen einer Schnittstelleversprachen. Konsumenten konnten über den Enterprise Service Bus mit einem Service in einer Version kommunizieren, die dieser bereits gar nicht mehrunterstützte. Das nötige Mapping übernahm der Enterprise Service Bus. Dies führte in der Praxis häufig aber zu dem neuen Problem, dass ein Team dafür verantwortlich war, diesen Enterprise Service Bus zu pflegen. Bei jedem Updateeiner Schnittstellenversion musste dieses ESB-Team jetzt bis zu n Mappings implementieren. Es war also bei jedem Schnittstellen-Update jedes Services involviert, was dieses Team häufig zu einem Bottleneck werden ließ.

Ein weiteres Problem von service-orientierten Architekturen war aus meiner Sicht ein falsches Verständnis von Kohäsion. Insbesondere die Tatsache, dass es bei Kohäsion darum geht, dass Funktionen auf gemeinsamen Daten an einer gemeinsamen Stelle liegen sollen, wurde häufig überinterpretiert. Es wurde ein unternehmensweit einheitliches Datenmodell gebildet. Für jedes Objekt daraus wurde ein eigener Service geschaffen.

Es wurde dann alles, was irgendwie nach Kunde klang in dem einen existierenden Customer-Service umgesetzt, anstatt zu schauen, in welchem fachlichen Kontext die Daten tatsächlich entstehen und in welchem sie benötigt wurden. Eine Lieferadresse für eine konkrete Bestellung z.B. hat primär erstmal nichts mit der Liste der Adressen eines Kunden zu tun, außer, dass sie eventuell daraus entstanden ist (sie kann aber im Bestellprozess auch von Hand eingegeben worden sein und nie wieder benötigt werden, z.B. weil es sich um die Adresse an einem Urlaubsort handelt). In der Praxis kam eine solche Funktionalität aber häufig in den allgemeinen Customer-Service oder sogar in einen allgemein zu verwendenden Address-Service. Ein solcher Service zeichnete sich dann wederdurch hohe Kohäsion noch durch geringe Kopplung aus.

 Die Ursache dieses Vorgehens ist nicht ganz klar. Entweder sie war eine Folge des Fokusses auf Wiederverwenbarkeit, nach dem Motto: „Wenn wir ein unternehmensweit einheitliches Datenmodellentwickeln und vorgeben, kann es überall verwendet werden“ oder ob es sich dabei eher um eine Folge dessen gehandelt hat, dass sich zu wenig Zeit für das Konzipieren der Fachlichkeit genommen wurde und dadurch z.B. einfach alles, was irgendwie nach Kunde klang einfach in den Customer-Service verschoben wurde, wodurch dieser dann alle Informationen über den Customer hatte und sich daraus das unternehmensweite Datenmodell entwickelte. Es wäre auch möglich, dass das einheitliche Datenmodell eine Folge der starren Schnittstellen war, also der Versuch, alle Schnittstellen-Objekte im Vorhinein zu definieren, um teure Schnittstellenänderungen zu vermeiden. Die Erkenntnis, dass sich Software immer weiterentwickelt und meistens in eine Richtung, die man nicht erwartet hat, gab es damals noch nicht. Ein Mind-Shift wurde erst durch das Agile Manifest in Gang gesetzt, das aber bis heute noch nicht in alle Unternehmen und insbesondere nicht in die bestehenden Software-Systeme durchgesickert ist.

 Eine Folge der Design-Entscheidungen in SOA-Architekturen war also in der Regel, dass in vielen der Services sowohl die Kohäsion nicht sehr hoch war als auch, dass eine hohe Kopplung zwischen den Services bestand. Für jeden Use-Case mussten z.B. Customer-Service, Address-Service, Product-Service usw. aufgerufen werden. Neben der bereits genannten architektonischen Herausforderung bei der Weiterentwicklung führte das auch dazu, dass service-orientierte Architekturen häufig eine geringe Laufzeit-Performance hatten und auch eine geringe Time-to-Market, weil für jedes Feature viele Services angefasst werden mussten.

Monolithen als logische Folge von SOA

In der Diskussion über Softwarearchitekturen werden monolithische Architekturen häufig gleichberechtigt neben service-orientierte Architekturen und Microservice-Architekturen dargestellt. Tatsächlich sind viele Monolithen jedoch nicht einfach nur ein alternatives Architekturkonzept, sondern für viele die logische Konsequenz aus gescheiterten SOA-Versuchen.

Die von Service-orientierten Architekturen versprochene klare Trennung von Verantwortlichkeiten durch verteilte, voneinander unabhängige Services entpuppte sich in der Realität häufig als das genaue Gegenteil: Services waren logisch stark miteinanderverflochten – etwa durch gemeinsame Datenmodelle, enge Prozessabhängigkeiten oder synchrone Kommunikation über Netzwerkgrenzen hinweg. Die Folge: Änderungen an einem Service zogen Änderungen an anderen nach sich, was die versprochene Autonomie ad absurdum führte.

In einem Monolithen hingegen lässt sich viel leichter mit hoher Kopplung umgehen. Eine Schnittstellenänderung kann per Refactoring in der IDE durchgeführt werden, anstatt aufwendig zwischen verschiedenen Teams abgesprochen und dann koordiniert released werden zu müssen.

Eine besondere Herausforderung in der SOA-Welt war die Transaktionalität über Service-Grenzen hinweg. Die meisten Geschäftsprozesse waren nicht in getrennte, lokal konsistente Schritte zerlegt. Stattdessen musste oft sichergestellt werden, dass mehrere Services gemeinsam erfolgreich oder gar nicht arbeiten – z. B. bei Bestellungen, Zahlungen und Lagerverwaltung. In der SOA-Welt mussten komplexe Patterns wie Web-Transaktionen, das Saga-Pattern oder Eventual Consistency genutzt werden, die schwer zu implementieren und zu testen waren (und sind) – und dennoch häufig zu inkonsistentem Verhalten führen.

Ein Monolith hat diese Komplexität nicht. Hier lassen sich solche Anforderungen durch ACID-Transaktionen, die über den gesamten Prozess gespannt werden, gut abbilden.

Aus diesen Erfahrungen heraus entschieden sich viele Teams ab Mitte der 2000er Jahre bewusst dazu, Services wieder zu Monolithen zusammenzuführen oder Grüne-Wiese-Projekte direkt als Monolith zu starten, um das System robuster, einfacher testbar und konsistenter zu machen. Die entstandenen Monolithen sind also nicht als Rückschritt zu sehen, sondern als bewusste Weiterentwicklung der praktischen Grenzen der SOA-Architekturen. In diesem Sinne sind Monolithen häufig auch keine Altlast, sondern eine konsequente Antwort auf die Herausforderungen schlecht definierter Service-Schnitte der SOA-Welt.

Domain-Driven Design schon früh mit Alternativen

Das Buch „Domain-Driven Design – Tackling Complexity in the Heart of Software” von Eric Evans erschien im August 2003 (siehe [2]). Während der erste Teil des Buches die modellgetriebene Entwicklung im engeren Sinne behandelt und viel Beachtung fand, wurde der zweite Teil – der sich mit strategischem Design beschäftigt – lange Zeit eher ignoriert. Dabei steckt gerade dort eine Vielzahl an Konzepten, die heute aktueller denn je sind. In diesem Teil des Buches beschäftigt sich Eric Evans nämlich damit, wie man große Systeme handhaben kann. Unter großen Systemen versteht er dabei Systeme, die nicht nur von einem Team entwickelt werden können, sondern wo Kommunikation zwischen Teams nötig ist.

Evans erkannte bereits 2003, dass in großen Systemen nicht ein einziges, einheitliches Modell alle fachlichen Anforderungen sinnvollabbilden kann. Stattdessen plädiert er dafür, komplexe Fachlichkeiten in kontextualisierte Teilmodelle, sogenannte Bounded Contexts, zu gliedern. Jeder dieser Kontexte bildet einen in sich geschlossenen Bedeutungsraum, in dem Begriffe und Regeln konsistent verwendet werden. Zwischen den Kontexten bestehen definierte Schnittstellen (auch im Modell), etwa über APIs, Events oder Übersetzungsschichten (Anticorruption Layer).

Diese Idee ist mehr als nur ein Architekturpattern – sie ist eine Antwort auf die Herausforderung, dass komplexe Systeme oft an der unklaren Abgrenzung von Verantwortlichkeiten und Begrifflichkeiten scheitern. Evans betont, dass Bounded Contexts begonnen mit dem Team-Schnitt über den Code bis hin zur Datenbank getrennt sein müssen und dass die Beziehungen zwischen den Bounded Contexts bewusst gestaltet werden müssen: etwa durch Partnerschaften, Open-Host-Services oder bewusst lose gekoppelte Integrationen.

Was damals noch weitgehend theoretisch blieb, ist aus heutiger Sicht der entscheidende Schritt, um Systeme nicht nur technisch, sondern entlang fachlicher Grenzen zu zerschneiden – und damit Komplexität beherrschbar zu machen.

Fazit: Fachliche Grenzen statt technischer Schichten

Mit dem von Eric Evans bereits 2004 in seinem Buch Domain-Driven Design beschriebenen Konzept der Bounded Contexts, hat er ein Prinzip eingeführt, das bis heute relevant ist: Komplexe Fachlichkeiten lassen sich nur dann dauerhaft verstehen und weiterentwickeln, wenn sie in klar abgegrenzte, konsistente Teilmodelle zerlegt werden. Entscheidend ist dabei nicht die eingesetzte Technologie, sondern die eindeutige fachliche Verantwortung innerhalb eines Kontexts und die bewusst gestalteten Schnittstellen zwischen ihnen.

Erst mit dem Aufkommen von Microservice-Architekturen erhielt dieser strategische Ansatz die Aufmerksamkeit, die er verdient. Methoden wie Event Storming unterstützen dabei, solche Grenzen kollaborativ zu identifizieren und ein gemeinsames Verständnis der Fachlichkeit zu schaffen. Im Mittelpunkt steht nicht die technische Umsetzung, sondern die Frage, wie das System aus Sicht der Domäne strukturiert ist.

Die Modularisierung eines Systems beginnt somit nicht im Code, sondern in der gemeinsamen Arbeit an der Domäne. Wer Software langfristig wartbar und anpassbar gestalten möchte, sollte technische Entscheidungen aus einem fundierten Verständnis der Fachlichkeit ableiten.
In den nächsten Artikeln zeige ich, wie sich auf Basis von Event-Storming-Ergebnissen konkrete Service-Schnitte und Zielarchitekturen entwickeln lassen und wie daraus ein praktikabler Weg zur Modularisierung eines Monolithen entsteht.

Quellen

[1] Structured design. Fundamentals of a discipline of computer programand systems design, Yourdon, E., Constantine, L. (1979)

[2] Domain-DrivenDesign – Tackling Complexity in the Heart of Software, Evans, E. (2003)

No items found.

Was ist ein gutes Modul?

Bereits 1979 haben sich Edward Yourdon und Larry L.Constantine in ihrem Buch „Structured Design: Fundamentals of a Discipline ofComputer Program and Systems Design” (siehe [1]) Gedanken darüber gemacht, was ein gutes Modul ausmacht. Sie haben dabei zwei Begriffe etabliert, die bisheute ihre Gültigkeit nicht verloren haben und herangezogen werden, wenn es um Modularisierung geht: Kopplung und Kohäsion.

 Ein gutes Modul zeichnet sich demnach vor allem durch diese zwei zentralen Qualitätsmerkmale aus: geringe Kopplung und hohe Kohäsion. Diese beiden Prinzipien sind entscheidend für die Wartbarkeit, Erweiterbarkeit und Verständlichkeit von Softwaresystemen.

 Kohäsion beschreibt, wie stark die Bestandteile eines Moduls fachlich zusammengehören. Ein kohäsives Modul bündelt Funktionen, die einem gemeinsamen Zweck dienen oder auf denselben Teil der Domäne bezogen sind. Hohe Kohäsion bedeutet, dass die Elemente eines Moduls eng miteinander verwandt sind – sie arbeiten auf denselben Daten, folgen einem gemeinsamen Prozess oder erfüllen eine klar umrissene Aufgabe. Dadurch wird das Modul verständlicher und lässt sich in der Regel einfacher testen, warten und weiterentwickeln.

 Kopplung hingegen beschreibt die Abhängigkeiten eines Moduls zu anderen Modulen. Ein gutentkoppeltes Modul kennt möglichst wenig über andere Teile des Systems und interagiert mit ihnen über wohldefinierte Schnittstellen. Dabei geht es bei Kopplung nicht nur um die Anzahl der Abhängigkeiten zu anderen Modulen, sondern auch darum, welcher Art diese Abhängigkeiten sind. Wenn die Abhängigkeit z.B. darin besteht, dass ein Modul ein anderes aufrufen muss, um dringend benötigte Daten zu bekommen, so ist die Kopplung höher, als wenn ein Modul ein anderes mittel seines asynchronen Events über Änderungen informiert, bei dem es egal ist, wann das Event zugestellt wird oder sogar, ob es überhaupt zugestellt wird.

 Geringe Kopplung sorgt dafür, dass Änderungen in einem Modul nicht zwangsläufig Auswirkungen auf andere Module haben. Das reduziert Seiteneffekte und erhöht die Robustheit und Flexibilität der Software.

Ein gutes Modul ist also kohärentin sich und möglichst unabhängig von seiner Umgebung. Es lässt sich isoliert betrachten, verstehen und ersetzen. Das Zusammenspiel von hoher Kohäsion und niedriger Kopplung ist ein zentrales Ziel bei der Gestaltungmodularer Architekturen – ob in monolithischen Systemen oder in Microservices. Nur so lassen sich langfristig stabile und evolvierbare Softwaresysteme bauen.

 

Fehler aus der SOA-Zeit

Ende des letzten Jahrtausends und zu Beginn dieses Jahrtausends wurde daraus ein Architektur-Stil entwickelt, der sich Service-Orientierten Architekturen (SOA)nannte. Die Idee der Unabhängigkeit wurde dabei insofern auf die Spitze getrieben, dass Module auch unabhängig deployed werden sollten. Jeder Service hatte also eine eigene Laufzeitumgebung. Beim Design dieser Architekturen wurde allerdings aus meiner Sicht häufig eine Reihe von Fehlern gemacht, auf die ich im Folgenden näher eingehen will.

Die Entwicklung eines Services war teuer und so wurde ein starker Fokus auf die Wiederverwendbarkeit eines Services gelegt. Ein Service war demnach besonders wichtig und wertvoll und damit gut, wenn er besonders häufig wiederverwendet wurde, also, wenn er besonders vielen Konsumenten hatte.

Das hatte allerdings verschiedene Probleme zur Folge. Das erste, naheliegende Problem ist die hohe Kopplung. Wenn ein Service von vielen Konsumenten verwendet wird, liegt ein hohes Maß an Kopplung vor, was genau den oben beschriebenen Kriterien eines guten Moduls widerspricht. In der Praxis zeigte sich auch tatsächlich genau das angesprochene Problem. Das Ändern einer Schnittstelle führte häufig dazu, dass alle Konsumenten auch angepasst werden mussten, was einen immensen Aufwand bedeutete. Schnittstellen wurden daher nur sehr selten geändert und waren in service-orientierten Architekturen sehr starr, was die Weiterentwicklung sehr aufwendig werden ließ, weil man entweder alte Schnittstellenversionen weiter pflegen musste oder eben bei jeder Schnittstellenänderung die Releases aller Clients mit koordinieren musste.

Enterprise Service Busses (ESB) versuchten das Problem zu lösen, indem sie u.A. die Möglichkeit eines Mappings zwischen verschiedenen Versionen einer Schnittstelleversprachen. Konsumenten konnten über den Enterprise Service Bus mit einem Service in einer Version kommunizieren, die dieser bereits gar nicht mehrunterstützte. Das nötige Mapping übernahm der Enterprise Service Bus. Dies führte in der Praxis häufig aber zu dem neuen Problem, dass ein Team dafür verantwortlich war, diesen Enterprise Service Bus zu pflegen. Bei jedem Updateeiner Schnittstellenversion musste dieses ESB-Team jetzt bis zu n Mappings implementieren. Es war also bei jedem Schnittstellen-Update jedes Services involviert, was dieses Team häufig zu einem Bottleneck werden ließ.

Ein weiteres Problem von service-orientierten Architekturen war aus meiner Sicht ein falsches Verständnis von Kohäsion. Insbesondere die Tatsache, dass es bei Kohäsion darum geht, dass Funktionen auf gemeinsamen Daten an einer gemeinsamen Stelle liegen sollen, wurde häufig überinterpretiert. Es wurde ein unternehmensweit einheitliches Datenmodell gebildet. Für jedes Objekt daraus wurde ein eigener Service geschaffen.

Es wurde dann alles, was irgendwie nach Kunde klang in dem einen existierenden Customer-Service umgesetzt, anstatt zu schauen, in welchem fachlichen Kontext die Daten tatsächlich entstehen und in welchem sie benötigt wurden. Eine Lieferadresse für eine konkrete Bestellung z.B. hat primär erstmal nichts mit der Liste der Adressen eines Kunden zu tun, außer, dass sie eventuell daraus entstanden ist (sie kann aber im Bestellprozess auch von Hand eingegeben worden sein und nie wieder benötigt werden, z.B. weil es sich um die Adresse an einem Urlaubsort handelt). In der Praxis kam eine solche Funktionalität aber häufig in den allgemeinen Customer-Service oder sogar in einen allgemein zu verwendenden Address-Service. Ein solcher Service zeichnete sich dann wederdurch hohe Kohäsion noch durch geringe Kopplung aus.

 Die Ursache dieses Vorgehens ist nicht ganz klar. Entweder sie war eine Folge des Fokusses auf Wiederverwenbarkeit, nach dem Motto: „Wenn wir ein unternehmensweit einheitliches Datenmodellentwickeln und vorgeben, kann es überall verwendet werden“ oder ob es sich dabei eher um eine Folge dessen gehandelt hat, dass sich zu wenig Zeit für das Konzipieren der Fachlichkeit genommen wurde und dadurch z.B. einfach alles, was irgendwie nach Kunde klang einfach in den Customer-Service verschoben wurde, wodurch dieser dann alle Informationen über den Customer hatte und sich daraus das unternehmensweite Datenmodell entwickelte. Es wäre auch möglich, dass das einheitliche Datenmodell eine Folge der starren Schnittstellen war, also der Versuch, alle Schnittstellen-Objekte im Vorhinein zu definieren, um teure Schnittstellenänderungen zu vermeiden. Die Erkenntnis, dass sich Software immer weiterentwickelt und meistens in eine Richtung, die man nicht erwartet hat, gab es damals noch nicht. Ein Mind-Shift wurde erst durch das Agile Manifest in Gang gesetzt, das aber bis heute noch nicht in alle Unternehmen und insbesondere nicht in die bestehenden Software-Systeme durchgesickert ist.

 Eine Folge der Design-Entscheidungen in SOA-Architekturen war also in der Regel, dass in vielen der Services sowohl die Kohäsion nicht sehr hoch war als auch, dass eine hohe Kopplung zwischen den Services bestand. Für jeden Use-Case mussten z.B. Customer-Service, Address-Service, Product-Service usw. aufgerufen werden. Neben der bereits genannten architektonischen Herausforderung bei der Weiterentwicklung führte das auch dazu, dass service-orientierte Architekturen häufig eine geringe Laufzeit-Performance hatten und auch eine geringe Time-to-Market, weil für jedes Feature viele Services angefasst werden mussten.

Monolithen als logische Folge von SOA

In der Diskussion über Softwarearchitekturen werden monolithische Architekturen häufig gleichberechtigt neben service-orientierte Architekturen und Microservice-Architekturen dargestellt. Tatsächlich sind viele Monolithen jedoch nicht einfach nur ein alternatives Architekturkonzept, sondern für viele die logische Konsequenz aus gescheiterten SOA-Versuchen.

Die von Service-orientierten Architekturen versprochene klare Trennung von Verantwortlichkeiten durch verteilte, voneinander unabhängige Services entpuppte sich in der Realität häufig als das genaue Gegenteil: Services waren logisch stark miteinanderverflochten – etwa durch gemeinsame Datenmodelle, enge Prozessabhängigkeiten oder synchrone Kommunikation über Netzwerkgrenzen hinweg. Die Folge: Änderungen an einem Service zogen Änderungen an anderen nach sich, was die versprochene Autonomie ad absurdum führte.

In einem Monolithen hingegen lässt sich viel leichter mit hoher Kopplung umgehen. Eine Schnittstellenänderung kann per Refactoring in der IDE durchgeführt werden, anstatt aufwendig zwischen verschiedenen Teams abgesprochen und dann koordiniert released werden zu müssen.

Eine besondere Herausforderung in der SOA-Welt war die Transaktionalität über Service-Grenzen hinweg. Die meisten Geschäftsprozesse waren nicht in getrennte, lokal konsistente Schritte zerlegt. Stattdessen musste oft sichergestellt werden, dass mehrere Services gemeinsam erfolgreich oder gar nicht arbeiten – z. B. bei Bestellungen, Zahlungen und Lagerverwaltung. In der SOA-Welt mussten komplexe Patterns wie Web-Transaktionen, das Saga-Pattern oder Eventual Consistency genutzt werden, die schwer zu implementieren und zu testen waren (und sind) – und dennoch häufig zu inkonsistentem Verhalten führen.

Ein Monolith hat diese Komplexität nicht. Hier lassen sich solche Anforderungen durch ACID-Transaktionen, die über den gesamten Prozess gespannt werden, gut abbilden.

Aus diesen Erfahrungen heraus entschieden sich viele Teams ab Mitte der 2000er Jahre bewusst dazu, Services wieder zu Monolithen zusammenzuführen oder Grüne-Wiese-Projekte direkt als Monolith zu starten, um das System robuster, einfacher testbar und konsistenter zu machen. Die entstandenen Monolithen sind also nicht als Rückschritt zu sehen, sondern als bewusste Weiterentwicklung der praktischen Grenzen der SOA-Architekturen. In diesem Sinne sind Monolithen häufig auch keine Altlast, sondern eine konsequente Antwort auf die Herausforderungen schlecht definierter Service-Schnitte der SOA-Welt.

Domain-Driven Design schon früh mit Alternativen

Das Buch „Domain-Driven Design – Tackling Complexity in the Heart of Software” von Eric Evans erschien im August 2003 (siehe [2]). Während der erste Teil des Buches die modellgetriebene Entwicklung im engeren Sinne behandelt und viel Beachtung fand, wurde der zweite Teil – der sich mit strategischem Design beschäftigt – lange Zeit eher ignoriert. Dabei steckt gerade dort eine Vielzahl an Konzepten, die heute aktueller denn je sind. In diesem Teil des Buches beschäftigt sich Eric Evans nämlich damit, wie man große Systeme handhaben kann. Unter großen Systemen versteht er dabei Systeme, die nicht nur von einem Team entwickelt werden können, sondern wo Kommunikation zwischen Teams nötig ist.

Evans erkannte bereits 2003, dass in großen Systemen nicht ein einziges, einheitliches Modell alle fachlichen Anforderungen sinnvollabbilden kann. Stattdessen plädiert er dafür, komplexe Fachlichkeiten in kontextualisierte Teilmodelle, sogenannte Bounded Contexts, zu gliedern. Jeder dieser Kontexte bildet einen in sich geschlossenen Bedeutungsraum, in dem Begriffe und Regeln konsistent verwendet werden. Zwischen den Kontexten bestehen definierte Schnittstellen (auch im Modell), etwa über APIs, Events oder Übersetzungsschichten (Anticorruption Layer).

Diese Idee ist mehr als nur ein Architekturpattern – sie ist eine Antwort auf die Herausforderung, dass komplexe Systeme oft an der unklaren Abgrenzung von Verantwortlichkeiten und Begrifflichkeiten scheitern. Evans betont, dass Bounded Contexts begonnen mit dem Team-Schnitt über den Code bis hin zur Datenbank getrennt sein müssen und dass die Beziehungen zwischen den Bounded Contexts bewusst gestaltet werden müssen: etwa durch Partnerschaften, Open-Host-Services oder bewusst lose gekoppelte Integrationen.

Was damals noch weitgehend theoretisch blieb, ist aus heutiger Sicht der entscheidende Schritt, um Systeme nicht nur technisch, sondern entlang fachlicher Grenzen zu zerschneiden – und damit Komplexität beherrschbar zu machen.

Fazit: Fachliche Grenzen statt technischer Schichten

Mit dem von Eric Evans bereits 2004 in seinem Buch Domain-Driven Design beschriebenen Konzept der Bounded Contexts, hat er ein Prinzip eingeführt, das bis heute relevant ist: Komplexe Fachlichkeiten lassen sich nur dann dauerhaft verstehen und weiterentwickeln, wenn sie in klar abgegrenzte, konsistente Teilmodelle zerlegt werden. Entscheidend ist dabei nicht die eingesetzte Technologie, sondern die eindeutige fachliche Verantwortung innerhalb eines Kontexts und die bewusst gestalteten Schnittstellen zwischen ihnen.

Erst mit dem Aufkommen von Microservice-Architekturen erhielt dieser strategische Ansatz die Aufmerksamkeit, die er verdient. Methoden wie Event Storming unterstützen dabei, solche Grenzen kollaborativ zu identifizieren und ein gemeinsames Verständnis der Fachlichkeit zu schaffen. Im Mittelpunkt steht nicht die technische Umsetzung, sondern die Frage, wie das System aus Sicht der Domäne strukturiert ist.

Die Modularisierung eines Systems beginnt somit nicht im Code, sondern in der gemeinsamen Arbeit an der Domäne. Wer Software langfristig wartbar und anpassbar gestalten möchte, sollte technische Entscheidungen aus einem fundierten Verständnis der Fachlichkeit ableiten.
In den nächsten Artikeln zeige ich, wie sich auf Basis von Event-Storming-Ergebnissen konkrete Service-Schnitte und Zielarchitekturen entwickeln lassen und wie daraus ein praktikabler Weg zur Modularisierung eines Monolithen entsteht.

Quellen

[1] Structured design. Fundamentals of a discipline of computer programand systems design, Yourdon, E., Constantine, L. (1979)

[2] Domain-DrivenDesign – Tackling Complexity in the Heart of Software, Evans, E. (2003)

No items found.

Weitere Artikel aus unserem Blog

Up Arrow