Monolith First in 2026: Warum du Microservices widerstehen solltest, bis es nötig ist

#Monolith First
Sandor Farkas - Founder & Lead Developer at Wolf-Tech

Sandor Farkas

Gründer & Lead Developer

Experte für Softwareentwicklung und Legacy-Code-Optimierung

Die Microservices-Diskussion hat sich leise umgekehrt. Nach einem Jahrzehnt, in dem das Aufteilen des Systems die Standardantwort auf fast jede Architekturfrage war, lernen Teams 2026 wieder etwas, das Martin Fowler schon 2015 geschrieben hat: Fang mit einem Monolithen an. Die Monolith First-Position war damals kontrovers. Heute ist sie fast Mainstream, vor allem weil viele Engineering-Organisationen die Rechnung für verteilte Systeme bezahlt haben und ihnen der Betrag nicht gefallen hat.

Das ist kein Argument dafür, dass Microservices falsch sind. Es ist ein Argument über Reihenfolge. Service-Grenzen sind teuer zu verschieben und vor mehrfachem Ausliefern des Produkts kaum korrekt zu erraten. Sie früh zu wählen heißt, sich auf eine Reihe von Annahmen festzulegen, genau in dem Moment, in dem du am wenigsten über deine Domäne weißt.

Was Monolith First tatsächlich behauptet

Die Behauptung ist eng gefasst und lohnt eine präzise Formulierung, weil sie in beide Richtungen karikiert wird.

Monolith First sagt nicht, dass Monolithen besser skalieren, dass sich Microservices nie auszahlen oder dass eine einzelne Codebasis per se einfacher ist. Es sagt, dass die Information, die für gute Service-Grenzen nötig ist, erst existiert, nachdem das System eine Weile in Produktion war und unter Änderungsdruck stand. In einem Monolithen verschiebst du eine Grenze mit einem Refactoring. In einer Microservice-Architektur wird dieselbe Änderung zu einer Migration mit zwei Deployments, einem API-Vertrag, einem Datenumzug und einem Zeitfenster für Rückwärtskompatibilität.

Es geht also um die Kosten des Irrtums. Am Anfang liegst du bei den Schnittstellen fast sicher falsch, und der Monolith ist die Architektur mit der billigsten Korrektur.

Die Kosten von Microservices vor Product-Market-Fit

Der Overhead eines verteilten Systems wird leicht unterschätzt, weil das meiste davon kein Code ist. Es ist operative Angriffsfläche, die auf einen Schlag ankommt.

Jeder In-Process-Aufruf wird zum Netzwerkaufruf. Ein Methodenaufruf, der Mikrosekunden dauerte und entweder funktionierte oder eine Exception warf, wird zu einem Request, der langsam sein, teilweise fehlschlagen, in einen Timeout laufen oder erst nach dem Aufgeben des Aufrufers gelingen kann. Jedes davon braucht eine Entscheidung: Retry-Policy, Idempotenz, Timeout-Budget, Circuit Breaker. Das ist echte Engineering-Arbeit für Rohrleitungen, die dein Produkt nicht verkauft.

Observability ist nicht mehr optional. In einem Monolithen erzählt ein Stacktrace die Geschichte. Über sieben Services erzeugt eine einzelne Nutzeraktion sieben Teilgeschichten, und ohne Distributed Tracing, durch jeden Hop propagierte Correlation IDs und Log-Aggregation kannst du sie nicht rekonstruieren. Diese Infrastruktur brauchst du ab Tag eins, nicht erst, wenn das Team groß genug ist, um sie zu rechtfertigen.

Schemaänderungen erfordern Koordination. Eine Spalte in einem Monolithen hinzuzufügen ist eine Migration in einem Deploy. Wenn zwei Services dieselbe konzeptuelle Entität lesen, wird dieselbe Änderung zu einem sequenzierten Rollout: Schema erweitern, Reader deployen, Backfill, Writer deployen, Schema verengen. Multipliziere das mit der Häufigkeit, in der ein Produkt in der Frühphase sein Datenmodell ändert, nämlich ständig.

Lokale Entwicklung wird schwerer. Neue Engineers brauchen das ganze System, um an einem Feature zu arbeiten, das zwei Services berührt. Docker-Compose-Dateien wachsen, das Onboarding wird langsamer, und die Feedback-Schleife dehnt sich von Sekunden auf Minuten.

Ownership wird unklar, bevor die Organisation groß genug ist, um sie klar zu machen. Microservices zahlen sich vor allem als Organisationswerkzeug aus: Sie erlauben unabhängigen Teams, ohne Abstimmung zu deployen. Mit zwölf Engineers hast du keine unabhängigen Teams. Du hast ein Team, das zwölf Deployment-Pipelines pflegt.

Keine dieser Kosten ist tödlich. Alle sind real, und alle kommen an, bevor die Vorteile es tun. Unsere Arbeit in Tech-Stack-Strategie existiert zu einem großen Teil, weil dieser Trade-off leicht in die Richtung falsch entschieden wird, die sich am anspruchsvollsten anfühlt.

Die Signale, die einen Split wirklich rechtfertigen

Aufteilen ist richtig, wenn der Monolith messbare Reibung verursacht, nicht wenn er sich unelegant anfühlt. Drei Signale sind verlässlich.

Das erste ist Deploy-Gleichschritt zwischen unabhängigen Teams. Wenn das Billing-Team nicht ausliefern kann, weil die Änderung des Reporting-Teams im selben Release Candidate steckt, und das wöchentlich statt gelegentlich passiert, sind die Koordinationskosten strukturell geworden. Beachte den Vorbehalt: Ein langsamer Release-Prozess ist oft ein CI- oder Testsuite-Problem, kein Architekturproblem. Behebe zuerst das Billigere.

Das zweite sind wirklich inkompatible Ressourcenprofile. Wenn ein Teil des Systems ein speicherhungriger Batch-Prozess ist und ein anderer eine latenzkritische API, und beide um dieselben Instanzen konkurrieren, hast du einen echten Grund zur Trennung. Es geht um die Form der Last, nicht um die Form des Codes. Eine Komponente, die auf zwanzig Instanzen skalieren muss, während alles andere zwei braucht, ist ein legitimer Extraktionskandidat.

Das dritte ist Datenbank-Contention durch inkompatible Zugriffsmuster. Lange analytische Abfragen, die Tabellen sperren, die transaktionale Schreibzugriffe brauchen, sind ein reales Problem. Prüfe aber zuerst, ob eine Read Replica es löst. Einen Service zu extrahieren, um ein Abfragemuster zu beheben, ist ein teurer Weg, sich etwas zu kaufen, das eine Replica an einem Nachmittag liefern kann.

Was kein Signal ist: Die Codebasis ist groß, ein Konferenzvortrag war überzeugend, ein neuer Kollege ist Microservices gewohnt, oder das Deploy dauert elf Minuten. Codebasisgröße adressierst du mit Modularität, und die braucht kein Netzwerk.

Der modulare Monolith: Grenzen ohne das Netzwerk

Das meiste, was Leute von Microservices wollen, sind Grenzen, nicht Verteilung. Ein modularer Monolith gibt dir das Erste ohne das Zweite: eine deploybare Einheit mit Modulen, die explizite, erzwungene Schnittstellen haben.

Praktisch heißt das, nach Geschäftsfähigkeit statt nach technischer Schicht zu organisieren. Statt Top-Level-Verzeichnissen Controller, Entity und Repository hast du Billing, Identity und Reporting, jedes mit eigenen Controllern, Entitäten und Services. Jedes Modul exponiert eine bewusst kleine öffentliche Oberfläche, typischerweise eine Handvoll Application Services und Read-Model-DTOs, und hält alles andere intern. Kommunikation zwischen Modulen läuft über diese öffentlichen Services oder über Domain Events, nie durch den Griff in die Entitäten oder Repositories eines anderen Moduls.

Die Disziplin, die das trägt, ist die Weigerung, Entitäten über Module hinweg zu teilen. Wenn Billing Kundendaten braucht, importiert es nicht die Identity-Entität. Es ruft einen Identity-Service auf, der ein DTO zurückgibt, oder es pflegt eine eigene minimale Kundenprojektion, die per Events aktualisiert wird. Diese eine Regel macht eine spätere Extraktion machbar, weil das Modul mit seinen Nachbarn bereits so spricht, wie ein Service es täte.

Über die Architektur selbst haben wir ausführlicher in wann Modular Monolith First die richtige Wahl ist geschrieben. Der Punkt, der sich hier zu wiederholen lohnt: Modularität ist eine Eigenschaft des Codes und des Teams, nicht der Deployment-Topologie.

Grenzen in Symfony mit PHPStan erzwingen

Konventionen, die nicht erzwungen werden, verfallen. In einer Symfony-Codebasis ist statische Analyse die günstigste verfügbare Durchsetzung, und sie läuft in der CI, statt von der Wachsamkeit der Reviewer abzuhängen.

Der Mechanismus ist unkompliziert. Mit Modulen unter src/Billing, src/Identity und src/Reporting, die ihre öffentliche API jeweils unter src/<Module>/Application exponieren, konfigurierst du PHPStan mit einer Regel, die jeder Klasse eines Modul-Namespaces verbietet, eine Klasse im nicht öffentlichen Namespace eines anderen Moduls zu referenzieren. Dafür gibt es Community-Extensions, und eine eigene Regel, die PHPStan\Rules\Rule implementiert, Klassenreferenzen inspiziert und Namespace-Präfixe vergleicht, umfasst vielleicht dreißig Zeilen. So oder so schlägt der Build fehl, wenn jemand App\Identity\Domain\Customer aus App\Billing heraus importiert.

Zwei Details machen den Unterschied zwischen einer Regel, die hilft, und einer, die das Team abschaltet. Erstens: Liefere sie mit einer Baseline aus. Eine bestehende Codebasis hat Hunderte Verstöße, und eine Regel, die den Build am ersten Tag rot macht, ist bis zur Mittagspause deaktiviert. Generiere eine Baseline, verbiete neue Verstöße und baue die bestehenden ab, während ihr den Code ohnehin anfasst. Zweitens: Führe eine explizite Allowlist für die legitimen Ausnahmen, etwa Value Objects im Shared Kernel, und reviewt sie. Wenn die Allowlist zu wachsen beginnt, ist das Information über eure Grenzen, kein Grund, die Regel aufzuweichen.

Das Ergebnis ist eine Codebasis, in der die Grenze real ist. Sie wird bei jedem Pull Request mechanisch geprüft, und wenn ein Modul wirklich ein Service werden muss, besteht die Extraktion größtenteils darin, einen Service-Aufruf durch einen HTTP- oder Message-Bus-Aufruf zu ersetzen, weil nie etwas anderes gekoppelt war. Diese Art der Durchsetzung ist ein Standardbestandteil dessen, worauf wir in Code-Quality-Consulting-Engagements achten, gerade weil sie den Drift verhindert, der spätere Entscheidungen teuer macht.

Später aufzuteilen ist kein Scheitern

Der Einwand gegen Monolith First lautet, dass es schmerzhaft ist, einen Service aus einem Monolithen zu extrahieren. Das kann es sein, aber dieser Schmerz ist eine Funktion davon, wie modular der Monolith war, nicht des Splits selbst. Ein sauber abgegrenztes Modul ohne geteilte Entitäten und mit eventbasierter Kommunikation zu extrahieren ist ein überschaubares Projekt. Aus einem echten Big Ball of Mud zu extrahieren ist furchtbar, was ein Argument für Modularität ist, nicht für verfrühte Verteilung.

Es lohnt sich auch, ehrlich über die Alternative zu sein. Microservices wieder zusammenzuführen, die entlang der falschen Linien geschnitten wurden, ist schwerer, als einen Monolithen entlang der richtigen zu teilen. Der Fehlermodus des Über-Aufteilens ist ein verteilter Monolith, in dem Services zusammen deployt werden müssen und sich eine Datenbank teilen, und du zahlst jetzt beide Rechnungen. Wenn du schon dort bist, führt der Weg zurück meist über inkrementelle Konsolidierung statt über einen Rewrite. Das ist das Terrain unserer Legacy-Code-Optimierung.

Wie du entscheidest

Schreib die konkrete Reibung auf, die du beseitigen willst, und frag dann, ob eine Service-Grenze der billigste Weg ist, sie zu beseitigen. Meistens lautet die ehrliche Antwort: eine Modulgrenze, eine Read Replica, eine schnellere Testsuite oder klarere Ownership innerhalb einer Codebasis. Wenn die Antwort wirklich ein Service ist, kannst du das Team benennen, das nicht deployen kann, die Last, die sich keine Instanzen teilen kann, oder das Zugriffsmuster, das sich keine Datenbank teilen kann. Kannst du es nicht benennen, kaufst du eine Versicherung gegen ein Problem, das du nicht hast, zu einer Prämie, die du heute zahlst.

Monolith First ist kein Konservatismus. Es ist der Erhalt der Option, billig falschzuliegen, und das ist am Anfang die wertvollste Option, die du hast.

Wenn du diese Entscheidung für einen Neubau triffst oder herausfinden willst, ob die Microservices, die du schon hast, dir helfen, schauen wir gern gemeinsam drauf. Schreib an hello@wolf-tech.io oder besuch wolf-tech.io. Wir machen das auch als fokussierte Architektur-Reviews innerhalb unserer Custom Software Development-Engagements.