Zum Inhalt springen
Projekt besprechen

24. September 2026

Der Monolith ist nicht das Problem. Der schlechte Monolith ist es.

RailsSoftwarearchitekturModularer MonolithMicroservicesModernisierung

Die falsche Gleichung: Monolith = Legacy

In vielen Architekturgesprächen wird „Monolith“ wie ein Synonym für „veraltet“ verwendet. Das ist unpräzise und führt zu teuren Entscheidungen. Ein Monolith beschreibt zunächst nur eine Deployment-Einheit: Anwendungsteile werden gemeinsam gebaut und ausgeliefert. Daraus folgt weder, dass der Code unwartbar ist, noch dass das System nicht skalieren kann.

Das eigentliche Problem ist meist ein schlecht strukturierter Monolith: fachliche Verantwortlichkeiten sind vermischt, Datenbanktabellen werden quer durch die Anwendung beschrieben, Abhängigkeiten sind implizit und jede Änderung erzeugt unerwartete Seiteneffekte. Wer diesen Zustand unverändert in Microservices überführt, verteilt nicht die Lösung. Er verteilt die Unordnung.

Gerade bei Rails-Anwendungen ist der Reflex zur Zerlegung häufig voreilig. Rails erleichtert es, schnell ein integriertes Produkt zu bauen. Ohne aktive Architekturarbeit kann das zu einem großen app/-Verzeichnis mit schwer erkennbaren Grenzen führen. Die richtige Gegenmaßnahme ist aber nicht automatisch ein Netzwerk aus Services. In vielen Fällen ist sie ein modularer Monolith.

Was ein modularer Monolith ausmacht

Ein modularer Monolith wird als eine Anwendung ausgeliefert, ist intern aber entlang fachlicher Domänen organisiert. Jede Domäne besitzt eine klar definierte Verantwortung, ein begrenztes öffentliches Interface und möglichst wenige Kenntnisse über die Interna anderer Domänen.

Nehmen wir eine typische B2B-Anwendung mit den Bereichen Kundenverwaltung, Abrechnung, Auftragsabwicklung und Benachrichtigungen. In einem unstrukturierten System kann jeder Bereich direkt auf Modelle und Tabellen der anderen zugreifen. Der Abrechnungs-Controller erzeugt dann vielleicht Aufträge, der Auftragsprozess verschickt E-Mails und ein Hintergrundjob ändert Kundenattribute. Die technische Kopplung wächst schneller als das Produkt.

In einem modularen Monolithen werden daraus explizite Bereiche:

app/
  domains/
    customers/
    orders/
    billing/
    notifications/

Die konkrete Ordnerstruktur ist zweitrangig. Entscheidend sind die Regeln dahinter:

  • Ein Modul besitzt seine fachlichen Regeln selbst.
  • Andere Module greifen nicht auf interne Klassen oder Tabellen zu, nur weil es bequem ist.
  • Kommunikation erfolgt über klar benannte Anwendungsfälle, Events oder stabile Interfaces.
  • Abhängigkeiten zwischen Modulen werden bewusst dokumentiert und getestet.
  • Gemeinsamer Code wird nicht vorschnell in ein „shared“-Verzeichnis verschoben. Häufig ist duplizierter, kleiner Code günstiger als eine künstliche gemeinsame Abstraktion.

Rails bietet dafür ausreichend Werkzeuge. Namespaces, Engines, Packwerk, gezielte Testgrenzen und eine konsequente Service- oder Use-Case-Schicht helfen, fachliche Grenzen sichtbar zu machen. Nicht jedes Projekt braucht all diese Mechanismen. Aber jedes Projekt braucht eine Antwort auf die Frage: Welcher Teil des Systems darf welchen anderen Teil direkt kennen?

Eine Datenbank ist nicht automatisch ein Architekturfehler

Der gemeinsame Datenbankzugriff wird oft als Hauptargument gegen Monolithen angeführt. Dabei wird Ursache und Wirkung verwechselt. Problematisch ist nicht primär eine Datenbank, sondern der unkontrollierte Zugriff auf fremde Daten und fremde Geschäftslogik.

Eine gemeinsame PostgreSQL-Instanz kann für viele Jahre eine sehr gute Wahl sein. Sie ermöglicht Transaktionen über fachliche Prozesse, reduziert Betriebsaufwand und vereinfacht Auswertungen sowie Backups. Relevant ist, dass Tabellen fachlich zugeordnet sind und Zugriffe darauf Regeln folgen.

Praktisch bedeutet das beispielsweise:

  • Das Modul Billing schreibt in seine eigenen Tabellen und verwaltet deren Migrationen.
  • Orders erzeugt keine Rechnungsdatensätze direkt, sondern ruft einen klaren Abrechnungsanwendungsfall auf oder veröffentlicht ein fachliches Ereignis.
  • Reporting-Abfragen werden als bewusstes Integrationsproblem behandelt, nicht als Freifahrtschein für beliebige Cross-Module-Joins.
  • Datenhoheit wird zuerst im Code und in den Verantwortlichkeiten etabliert. Eine physisch getrennte Datenbank kann später folgen, muss aber nicht der erste Schritt sein.

Diese Disziplin ist keine theoretische Übung. Sie senkt die Kosten von Änderungen. Wenn ein Team weiß, wo eine Regel lebt und über welchen Weg sie aufgerufen wird, sind Refactorings kalkulierbar. Wenn jede Tabelle öffentlich ist, wird jede Schemaänderung zum Risiko.

Der verteilte Monolith: teuer, fragil und schwer zu ändern

Microservices lösen bestimmte Probleme gut. Sie bringen aber neue Probleme mit, die in frühen Produktphasen oft größer sind als ihr Nutzen.

Aus einem Methodenaufruf wird ein Netzwerkaufruf. Damit kommen Timeouts, Retries, Duplikate, Versionskompatibilität, verteiltes Tracing, partielle Ausfälle und asynchrone Fehlerbehandlung hinzu. Eine Transaktion über mehrere Bereiche wird nicht mehr durch die Datenbank garantiert, sondern muss fachlich modelliert werden: etwa mit Zustandsautomaten, Outbox-Pattern, Idempotenz und Kompensationslogik.

Das ist sinnvoll, wenn diese Komplexität durch echte Anforderungen gerechtfertigt ist. Es ist nicht sinnvoll, sie einzuführen, weil ein Architekturdiagramm dadurch moderner aussieht.

Ein häufiges Ergebnis vorzeitiger Zerlegung ist der verteilte Monolith:

  • Services werden gemeinsam deployt, weil Änderungen sonst nicht kompatibel sind.
  • Ein Service liest direkt aus der Datenbank eines anderen.
  • Gemeinsame Libraries enthalten immer mehr Fachlogik.
  • Ein Ausfall im Netzwerk blockiert zentrale Geschäftsprozesse.
  • Lokale Entwicklung und Integrationstests benötigen mehrere Container, Queues und externe Umgebungen.
  • Die Betriebskosten steigen, ohne dass Teams unabhängiger liefern können.

Dann existieren die Nachteile verteilter Systeme bereits, aber nicht deren Vorteile.

Deployments: Ein Monolith kann sehr gut auslieferbar sein

„Wir brauchen Microservices für unabhängige Deployments“ ist ein verständliches Argument, aber kein Automatismus. Viele Anwendungen werden nicht deshalb langsam ausgeliefert, weil sie eine gemeinsame Deployment-Einheit haben. Sie werden langsam ausgeliefert, weil Tests instabil sind, Releases zu groß werden, Datenmigrationen riskant sind oder Verantwortlichkeiten im Team unklar bleiben.

Ein Rails-Monolith kann mit wenigen, verlässlichen Praktiken sehr gut deploybar sein:

  • kleine, rückwärtskompatible Änderungen statt großer Release-Pakete,
  • Feature Flags für schrittweise Aktivierung,
  • expandierende und später bereinigende Datenbankmigrationen,
  • Hintergrundjobs für lang laufende Verarbeitung,
  • Monitoring für Fehler, Latenzen und Queue-Tiefen,
  • automatisierte Tests an den Modulgrenzen,
  • klarer Rollback- oder Forward-Fix-Prozess.

Bei Datenbankänderungen ist das sogenannte Expand-Contract-Vorgehen besonders wichtig. Zuerst wird ein neues Feld oder eine neue Tabelle ergänzt. Dann können alter und neuer Code parallel funktionieren. Erst wenn die Umstellung abgeschlossen ist, werden alte Strukturen entfernt. Das reduziert Deployment-Risiken deutlich, unabhängig davon, ob das System ein Monolith oder ein Service-Verbund ist.

Wann Microservices tatsächlich sinnvoll werden

Microservices sind kein Reifegrad-Abzeichen. Sie sind ein Werkzeug für konkrete organisatorische und technische Situationen. Eine Aufteilung wird plausibel, wenn mehrere der folgenden Bedingungen dauerhaft erfüllt sind:

  1. Unabhängige Teams haben echte, stabile Domänenverantwortung. Nicht zwei Personen mit unterschiedlichen Tickets, sondern Teams, die einen Bereich über längere Zeit eigenständig entwickeln und betreiben.
  2. Unterschiedliche Skalierungsprofile verursachen relevante Kosten oder Risiken. Beispielsweise benötigt ein Medienverarbeitungsdienst massiv mehr Rechenleistung als die Kernanwendung.
  3. Unterschiedliche Verfügbarkeitsanforderungen müssen isoliert werden. Ein Ausfall der Volltextsuche darf etwa nicht den Checkout blockieren.
  4. Eine fachliche Grenze ist über längere Zeit stabil. Die Organisation und das Domänenmodell sollten nicht monatlich neu geschnitten werden.
  5. Der Betrieb verteilter Systeme ist beherrscht. Dazu gehören Observability, Incident-Prozesse, CI/CD, Secret Management, Messaging-Kompetenz und Ownership im Betrieb.
  6. Die Entkopplung schafft messbaren Nutzen. Kürzere Durchlaufzeiten, geringere Infrastrukturkosten, bessere Ausfallsicherheit oder klarere Teamautonomie sollten überprüfbar sein.

Ein guter erster Kandidat ist oft kein Kernbereich mit vielen synchronen Geschäftsregeln, sondern ein klar abgegrenzter Randbereich: Dokumentenerzeugung, Importverarbeitung, Suchindexierung, Medienkonvertierung oder eine externe Integrationsschnittstelle. Dort lassen sich technische und fachliche Grenzen häufig sauberer ziehen.

Modernisierung ohne Big Bang

In Modernisierungsprojekten treffe ich regelmäßig auf die Annahme, dass ein großer Altmonolith nur durch einen vollständigen Neubau oder eine umfassende Service-Zerlegung zu retten sei. Beides ist selten notwendig und fast immer riskant.

Ein sinnvoller Weg beginnt mit Transparenz:

  1. Domänen und Verantwortlichkeiten kartieren. Welche Geschäftsprozesse gibt es? Welche Modelle, Tabellen, Jobs und APIs gehören jeweils dazu?
  2. Kopplungen sichtbar machen. Direkte Modellzugriffe, zyklische Abhängigkeiten, gemeinsame Tabellen und unklare Ownership sind konkrete Modernisierungskandidaten.
  3. Eine Grenze nach der anderen stärken. Neue Anforderungen werden nur noch über definierte Interfaces umgesetzt. Bestehende direkte Zugriffe werden schrittweise abgebaut.
  4. Testbarkeit und Beobachtbarkeit erhöhen. Ohne verlässliche Tests und Produktionsdaten über Fehlerbilder bleibt jede Architekturentscheidung spekulativ.
  5. Erst dann Auslagerungen prüfen. Wenn ein Modul eine stabile Schnittstelle und eigene Verantwortung besitzt, kann es bei Bedarf als Service extrahiert werden.

Dieses Vorgehen ähnelt dem Strangler Pattern, muss aber nicht zwangsläufig in einer Microservice-Landschaft enden. Sein Vorteil liegt gerade darin, Entscheidungen aufzuschieben, bis reale Anforderungen vorliegen.

Architekturberatung: Die Kosten der Komplexität mitrechnen

Architekturentscheidungen sollten nicht nur nach Skalierbarkeit auf dem Whiteboard bewertet werden. Relevant sind die Gesamtkosten über Entwicklung, Betrieb und Veränderung.

Ein einzelnes Rails-System braucht in der Regel weniger Infrastruktur, weniger Deployment-Pipelines, weniger Netzwerk- und Sicherheitskonfiguration sowie weniger verteiltes Debugging. Für ein kleines oder mittelgroßes Team bedeutet das oft: mehr Zeit für Produktarbeit und weniger Zeit für Plattformpflege.

Das ist keine Aufforderung, technische Schulden zu ignorieren. Im Gegenteil: Ein modularer Monolith verlangt Disziplin. Domänengrenzen müssen verteidigt, Abhängigkeiten geprüft und technische Regeln in Code Reviews sowie Tests verankert werden. Der Unterschied ist, dass diese Arbeit gezielt Komplexität reduziert, statt neue Betriebsprobleme einzuführen.

In der Architecture Consulting ist deshalb die erste Frage nicht: „Wie teilen wir den Monolithen auf?“ Sie lautet: „Welche Änderung ist heute zu teuer, zu riskant oder zu langsam – und warum?“

Wenn die Ursache eine fehlende Grenze im Code ist, hilft Modularisierung. Wenn ein Batch-Job die Infrastruktur dominiert, kann eine Extraktion sinnvoll sein. Wenn Teams sich gegenseitig bei jedem Release blockieren, müssen zuerst Verantwortlichkeiten, Schnittstellen und Deployment-Prozesse betrachtet werden. Die Architektur folgt dem Problem, nicht dem Trend.

Fazit

Ein Monolith ist nicht automatisch Legacy. Ein unstrukturierter Monolith ist ein Problem, weil er Änderungen unvorhersehbar und teuer macht. Microservices beheben dieses Problem nicht von selbst; ohne klare Domänengrenzen verschieben sie es in Netzwerk, Betrieb und Organisation.

Für viele Rails-Anwendungen ist ein modularer Monolith die wirtschaftlichere und robustere Architektur: eine Deployment-Einheit, klare fachliche Verantwortlichkeiten, kontrollierte Datenzugriffe und ein nachvollziehbarer Weg zur späteren Extraktion einzelner Komponenten.

Die beste Architektur ist nicht die mit den meisten Services. Sie ist die, deren Komplexität zum Produkt, zum Team und zu den tatsächlichen Betriebsanforderungen passt.