"Wir sollten das in Microservices aufteilen." Dieser Satz fällt erstaunlich zuverlässig, sobald jemand zum ersten Mal auf einen gewachsenen Legacy-Monolithen schaut. Microservices klingen modern, skalierbar und nach einer sauberen Lösung. Meistens sind sie das Gegenteil davon – zumindest für kleine Teams und mittelständische Betriebe.
Warum Microservices so verlockend klingen
Der Reiz ist verständlich: Jeder Service hat eine klare Aufgabe. Teams können unabhängig deployen. Technologien lassen sich pro Service wählen. Netflix macht das so. Amazon macht das so. Das muss doch richtig sein.
Das Problem: Netflix und Amazon haben Tausende von Entwicklern. Ein Mittelständler hat vielleicht fünf. Die Probleme, die Microservices lösen, hat man in dieser Größenordnung schlicht nicht. Dafür bekommt man jede Menge neue.
Was Microservices wirklich kosten
Verteilte Systeme sind inhärent komplexer als monolithische. Netzwerklatenz, Timeouts, inkonsistente Zustände zwischen Services – das sind echte Probleme, die man in einem Monolithen einfach nicht hat. Dazu kommt:
Deployment-Overhead: Jeder Service braucht eine eigene Pipeline, eigenes Monitoring, eigene Logs. Statt einem System wartet man zehn. Wer macht das on-call um 2 Uhr nachts, wenn Service 3 nicht mit Service 7 kommuniziert?
Transaktionen: In einem Monolithen ist eine Datenbanktransaktion eine Zeile Code. In verteilten Systemen wird daraus ein Saga-Pattern oder Two-Phase-Commit – beides komplexer und fehleranfälliger.
Was stattdessen sinnvoller ist: Der modulare Monolith
Das eigentliche Problem bei Legacy-Monolithen ist nicht, dass sie monolithisch sind. Das Problem ist, dass sie unstrukturiert sind. Jede Funktion kennt jede andere Funktion. Domänengrenzen existieren nicht. Das lässt sich innerhalb des Monolithen lösen.
Ein modularer Monolith trennt Domänen durch Namespaces und klare Interfaces, ohne die Deploymenteinheit aufzuteilen:
// Vorher: alles kennt alles
function verarbeiteBestellung($bestellId) {
sendeBestellbestaetigung($bestellId);
aktualisiereBestand($bestellId);
erstelleRechnung($bestellId);
}
// Nachher: Domänen mit klaren Interfaces
namespace Bestellung;
class BestellService {
public function __construct(
private readonly Benachrichtigung\BenachrichtigungService $benachrichtigung,
private readonly Lager\LagerService $lager,
private readonly Buchhaltung\RechnungsService $rechnung,
) {}
public function verarbeite(int $bestellId): void {
$this->benachrichtigung->sendeBestaetigung($bestellId);
$this->lager->aktualisiereBestand($bestellId);
$this->rechnung->erstelle($bestellId);
}
}
Die Domänen sind getrennt, die Abhängigkeiten sind explizit – aber es ist ein Deployment, eine Datenbank, ein Monitoring. Wenn später echte Microservices sinnvoll werden, sind die Grenzen bereits gezogen.
Ein gut strukturierter Monolith ist besser als schlecht getrennte Microservices. Microservices lösen keine Architekturprobleme – sie verteilen sie nur über das Netzwerk.
Wann Microservices doch sinnvoll sind
Es gibt Situationen, in denen die Aufteilung echten Sinn ergibt: wenn einzelne Komponenten völlig unterschiedliche Skalierungsanforderungen haben, wenn Teams wirklich unabhängig deployen müssen weil verschiedene Deployment-Zyklen existieren, oder wenn die Organisation groß genug ist, um den Betriebsaufwand zu tragen. Für die meisten mittelständischen Betriebe trifft keines dieser Kriterien zu.
Fazit
Der Monolith ist nicht das Problem. Die fehlende Struktur ist das Problem. Ich löse fehlende Struktur, indem ich Struktur einführe – nicht indem ich das System in zwölf Services aufteile und die Komplexität über ein Netzwerk verteile. Der modulare Monolith ist in den meisten Fällen der sinnvollste Zwischenschritt, und bei vielen Betrieben auch das sinnvolle Ziel.