Das Versprechen der Microservices und die ernüchternde Realität
Microservices galten lange als Standardantwort auf fast jedes Problem moderner Softwareentwicklung. Unabhängig deploybare Services, individuelle Skalierung und kleine, autonome Teams versprechen hohe Geschwindigkeit. Für große Plattformen mit klar getrennten Geschäftsbereichen kann dieses Modell sehr gut funktionieren. Es ist jedoch kein kostenloser Architekturgewinn, sondern ein Tauschgeschäft: Weniger Kopplung im Quellcode bedeutet häufig mehr Komplexität im Betrieb.
In der Praxis entstehen schnell zusätzliche Kosten für Containerplattformen, Service-Meshes, zentrale Observability, Secrets-Management, CI/CD-Pipelines und Bereitschaftsdienste. Jeder Remote-Aufruf bringt außerdem Netzwerkfehler, Serialisierung und Latenz in die Anwendung. Transaktionen, die früher innerhalb eines Prozesses und einer Datenbank stattfanden, müssen plötzlich über mehrere Services koordiniert werden. Selbst die Fehlersuche wird anspruchsvoller, weil ein einzelner Geschäftsfall über Logs, Traces und Metriken vieler Komponenten verteilt sein kann.
Der modulare Monolith setzt an einem anderen Punkt an. Er verbindet fachlich getrennte Module mit einem einzigen deploybaren Artefakt. Dadurch bleiben klare Verantwortlichkeiten, Kapselung und testbare Schnittstellen erhalten, während die wesentlichen Kosten verteilter Systeme zunächst entfallen. Für wachsende Teams ist das häufig der pragmatischere Startpunkt: strukturiert genug für langfristige Entwicklung, einfach genug für einen verlässlichen Betrieb.

Was einen modularen Monolithen von Altlasten unterscheidet
Ein modularer Monolith ist eine einzelne Anwendung, die intern aus unabhängig gedachten Modulen besteht. Jedes Modul bündelt eine fachliche Fähigkeit, etwa Bestellungen, Zahlungen, Benutzerverwaltung oder Versand. Es besitzt eigene Anwendungslogik, eigene Datenzugriffe und eine klar definierte öffentliche Schnittstelle. Andere Module greifen nicht direkt auf interne Klassen, Tabellen oder Repositories zu.
Damit unterscheidet sich die Architektur grundlegend vom gewachsenen Spaghetti-Monolithen. In einer solchen Altlast kann jede Schicht auf jede andere zugreifen. Ein Controller ruft direkt ein Repository eines fremden Fachbereichs auf, Datenbanktabellen werden gemeinsam verändert und kleine Änderungen lösen unvorhersehbare Seiteneffekte aus. Der modulare Monolith macht diese Abhängigkeiten sichtbar und begrenzt sie. Module kommunizieren innerhalb desselben Prozesses, aber nach Regeln, die denen von Services ähneln.
Im Gegensatz zu historisch gewachsenen Systemen, wie sie im Kontext klassischer monolithischer Anwendungen beschrieben werden, erzwingt die modulare Variante saubere Schnittstellen. Typische Architekturmerkmale sind:
- fachlich geschnittene Module statt ausschließlich technischer Schichten
- private Implementierungsdetails und öffentliche APIs
- eigene Datenzugriffsschichten oder getrennte Datenbankschemata
- In-Process-Aufrufe ohne Netzwerk- und Serialisierungsaufwand
- Architekturtests, die unerlaubte Abhängigkeiten und Zyklen verhindern
- gemeinsame Build-, Test- und Deployment-Artefakte
Eine wichtige Grenze liegt zwischen gemeinsamer Infrastruktur und gemeinsamer Fachlogik. Logging, Konfiguration oder Transaktionsmechanismen dürfen zentral bereitgestellt werden. Fachliche Regeln sollten dagegen im verantwortlichen Modul bleiben. Wenn das Bestellmodul auf interne Klassen des Zahlungsmoduls zugreift, ist die Trennung nur äußerlich. Saubere APIs und gegebenenfalls Domänenereignisse schaffen hier belastbare Grenzen. Zu gesprächige Module sind zudem ein Warnsignal: Müssen zwei Bereiche ständig gegenseitig Daten anfordern, ist der fachliche Schnitt möglicherweise falsch gewählt.
Direkter Systemvergleich zwischen Monolith und Microservices
Der zentrale Vorteil des modularen Monolithen liegt in der Kombination aus struktureller Ordnung und betrieblicher Einfachheit. Ein einzelnes Artefakt lässt sich bauen, testen, versionieren und ausrollen. Bei einem Microservice-System muss dagegen jede Änderung auf Kompatibilität zwischen mehreren Versionen, Verträge, Netzwerkpfade und voneinander abhängige Deployments geprüft werden. Das bedeutet nicht, dass Microservices grundsätzlich langsam sind. Es bedeutet, dass ihre Geschwindigkeit erst durch eine leistungsfähige Plattformorganisation entsteht.
| Kriterium | Modularer Monolith | Microservices |
|---|---|---|
| Betriebskosten | Wenige Laufzeitkomponenten und zentrale Betriebsprozesse | Mehrere Deployments, Plattformen, Überwachung und Bereitschaftsaufwand |
| Kommunikation | In-Process-Aufrufe mit geringer Latenz | Netzwerkaufrufe mit Latenz, Timeouts und Serialisierung |
| Deployment | Ein Artefakt, dafür gemeinsames Release | Unabhängige Releases, aber höherer Koordinations- und Plattformaufwand |
| Fehlersuche | Zentrale Logs und einfacher lokaler Debugging-Pfad | Verteilte Traces, Korrelation und zusätzliche Ausfallpunkte |
| Skalierung | Skalierung der gesamten Anwendung | Selektive Skalierung einzelner Services |
| Transaktionen | Einfacher innerhalb einer Datenbank möglich | Verteilte Konsistenz und oft asynchrone Prozesse erforderlich |
Auch organisatorisch ist die Entscheidung relevant. Conway’s Law beschreibt vereinfacht, dass Systeme häufig die Kommunikationsstrukturen ihrer Organisation widerspiegeln. Wenn drei kleine Teams an einem gemeinsamen Produkt arbeiten, erzeugt eine Aufteilung in zehn Services nicht automatisch mehr Autonomie. Stattdessen entstehen Abstimmungen über API-Verträge, Releases, Betrieb und Verantwortlichkeiten. Ein modularer Monolith kann fachliche Zuständigkeiten bereits sauber abbilden, ohne jede Grenze als eigenes Infrastrukturprojekt zu behandeln.
Das gemeinsame Deployment-Artefakt spart im Alltag vor allem bei kleinen und mittleren Änderungen Zeit. Ein Entwickler kann einen Anwendungsfall durch mehrere Module verfolgen, lokal debuggen und mit einem Integrationstest absichern. Der Preis dafür ist ein gemeinsames Release und eine gemeinsame Laufzeitskalierung. Diese Nachteile sind real, aber häufig leichter zu beherrschen als ein verteiltes System, das zu früh eingeführt wurde. Eine DDD-orientierte, vertikal geschnittene Struktur unterstützt dabei die Trennung, ohne den Betrieb unnötig zu vervielfachen.
Praktische Implementierung mit modernen Frameworks
Die technische Umsetzung beginnt nicht mit einem Framework, sondern mit Regeln. Jedes Modul sollte einen eigenen Einstiegspunkt für Anwendungsfälle besitzen. Controller, Handler oder Commands delegieren an die zuständige Anwendungsschicht. Die Domäne kennt weder HTTP noch konkrete Datenbanktreiber. Adapter übernehmen die Verbindung nach außen, etwa zu einer Datenbank, einem Nachrichtensystem oder einem externen Zahlungsanbieter.
Dependency Injection und Inversion of Control helfen, diese Grenzen im Code durchzusetzen. Abhängigkeiten werden gegen Abstraktionen formuliert und von außen bereitgestellt. Dadurch kann ein Modul mit Test-Doubles geprüft werden, ohne seine Infrastruktur hochzufahren. Für Java-Teams erklärt das Tutorial Was ist das Spring Framework die Grundlagen von IoC, Dependency Injection und weiteren Spring-Konzepten. Spring Boot eignet sich mit seinen Konfigurationen, Testwerkzeugen und Modulen als Fundament für umfangreiche Backends.
Auch NestJS bietet für TypeScript-Anwendungen eine passende Struktur. Module, Provider, Guards und Interceptors machen Abhängigkeiten sichtbar und fördern eine klare Komposition. Die Wahl zwischen Spring Boot und NestJS sollte jedoch von Laufzeitprofil, vorhandenen Kompetenzen, Supportmodell und Teamkontext abhängen. CPU-intensive Aufgaben können von der JVM profitieren, während I/O-lastige Anwendungen mit TypeScript eine durchgängige Sprache über Frontend und Backend ermöglichen können.
- Ordne den Code zuerst nach Geschäftsfähigkeiten und Anwendungsfällen, nicht nur nach Controllern und Repositories.
- Definiere pro Modul öffentliche Befehle, Abfragen und Ereignisse.
- Verbiete direkte Importe fremder Infrastruktur- und Datenzugriffsschichten.
- Nutze In-Memory-Events für lose gekoppelte Reaktionen innerhalb des Prozesses.
- Prüfe Modulgrenzen mit Architekturtests und automatisierten Abhängigkeitsregeln.
- Halte Integration-Event-Verträge stabil, auch wenn die Implementierung intern verändert wird.
In-Memory-Events sind dabei kein Ersatz für ein dauerhaftes Messaging-System. Sie bieten zunächst eine einfache Möglichkeit, Module asynchron zu entkoppeln. Wird ein Modul später als eigener Service herausgelöst, kann der interne Bus durch einen Broker ersetzt werden. Wichtig bleibt, dass Ereignisse fachlich formuliert sind und keine internen Datenstrukturen nach außen leaken. So entsteht ein realistischer Extraktionspfad statt einer bloßen Ansammlung technischer Schichten.
Entscheidungsmatrix für den Architekturschnitt
Der modulare Monolith ist besonders stark, wenn die fachliche Struktur noch nicht vollständig verstanden ist. Frühe Microservices machen falsche Grenzen teuer, weil jede Korrektur API-Verträge, Datenhaltung und Deploymentprozesse betrifft. Innerhalb eines modularen Monolithen können Module dagegen leichter umbenannt, zusammengelegt oder neu geschnitten werden. Das reduziert das Risiko, sich zu früh auf eine unpassende Domänenaufteilung festzulegen.
Fünf Indikatoren sprechen häufig für den modularen Monolithen:
- Das Team ist klein oder mittelgroß und verfügt noch nicht über eine ausgereifte Plattform- und DevOps-Organisation.
- Die meisten Anwendungsfälle benötigen gemeinsame Transaktionen oder konsistente Datenänderungen.
- Die erwartete Last lässt sich durch horizontale Skalierung der gesamten Anwendung bewältigen.
- Die fachlichen Grenzen sind noch in Bewegung und werden durch neue Produktanforderungen weiter geschärft.
- Der größte aktuelle Schmerz liegt in unübersichtlichem Code, nicht in der unabhängigen Skalierung einzelner Komponenten.
Microservices sind dennoch sinnvoll, wenn unabhängige Skalierung oder Deployment ein nachweisbarer Geschäftsvorteil ist. Das gilt etwa für stark unterschiedliche Lastprofile, gesetzlich oder organisatorisch getrennte Verantwortungsbereiche, klar isolierbare Sicherheitszonen oder Komponenten mit zwingend anderer Technologie. Auch besonders kritische Ausfallgrenzen können eine Trennung rechtfertigen. Entscheidend ist der konkrete Bedarf, nicht die Größe des Quellcodes oder die Popularität eines Architekturtrends.
So gehst du bei der Prüfung der bestehenden Codebasis vor:
- Erfasse die wichtigsten Geschäftsprozesse und ordne ihre Daten, Regeln und Verantwortlichkeiten zu.
- Markiere direkte Zugriffe zwischen fachlichen Bereichen, gemeinsame Tabellen und zyklische Abhängigkeiten.
- Miss reale Betriebsdaten wie Antwortzeiten, Fehlerraten, Lastspitzen und Build-Dauer.
- Bewerte Teamgröße, Bereitschaftsmodell, Testautomatisierung, Plattformkompetenz und Releasefähigkeit.
- Formuliere für jede mögliche Service-Auslagerung einen messbaren Grund, etwa geringere Kosten oder unabhängige Skalierung.
- Extrahiere erst dann einen Service, wenn die Modulgrenze stabil ist und der operative Nutzen die zusätzliche Komplexität rechtfertigt.
Besondere Vorsicht ist bei einem verteilten System mit weiterhin gemeinsamer Datenbank geboten. Dadurch entstehen oft die Nachteile beider Welten: Netzwerkkomplexität und mehrere Deployments, aber weiterhin starke Kopplung über ein Datenmonolith. Ein modularer Monolith ist in diesem Fall meist der ehrlichere und leichter kontrollierbare Zustand. Erst wenn Datenhoheit, Verantwortlichkeit und Schnittstellen tatsächlich getrennt werden können, ist die Auslagerung nachhaltig.
Den Mut zur architektonischen Einfachheit wiederentdecken
Ein modularer Monolith ist kein Rückschritt. Er ist eine bewusste Architekturentscheidung, die fachliche Trennung von physischer Verteilung unterscheidet. Gute Module schaffen klare Verantwortlichkeiten, testen Geschäftslogik isoliert und halten spätere Extraktion offen. Gleichzeitig bleiben Build, Betrieb, Debugging und Transaktionsmanagement überschaubar.
Der nächste Schritt muss keine vollständige Migration sein. Häufig genügt ein gezielter Entflechtungsplan:
- einen besonders unübersichtlichen Geschäftsbereich als erstes Modul abgrenzen
- öffentliche Schnittstellen definieren und direkte Fremdzugriffe entfernen
- Architekturtests gegen neue Kopplung einführen
- gemeinsame Datenzugriffe schrittweise in die verantwortlichen Module verschieben
- Messgrößen für Deploymentzeit, Fehlersuche, Laufzeit und Betriebskosten verfolgen
Die passende Architektur ist jene, die den Geschäftswert zuverlässig unterstützt. Microservices können dafür ein ausgezeichnetes Mittel sein, aber sie sind kein Reifezeugnis an sich. Für viele Teams liefert ein diszipliniert modularer Monolith zunächst die bessere Balance aus Performance, Änderbarkeit und Betriebssicherheit. Wenn später ein Modul tatsächlich unabhängig skalieren, deployen oder betrieben werden muss, steht mit dieser Struktur ein belastbarer Weg zur gezielten Auslagerung bereit.

