Ich habe vier Jahre damit verbracht, Services aus einem Monolithen zu extrahieren, und die nächsten vier damit, sie still wieder zusammenzuführen. Der modulare Monolith ist keine Niederlage — er ist eine Design-Entscheidung, die Optionalität offenhält und die Deploy-Geschichte langweilig macht. Dieser Post ist das Argument dafür, ihn mit Absicht zu wählen — mit den Belegen aus der Migration, die mir die Lektion auf die harte Tour beigebracht hat.

Die Extraktions-Ära

Von 2014 bis 2018 arbeitete ich an Krankenhaus-Plattformen (AFAQ, HAKEEM), Recruitment-Plattformen (Talentera) und ein paar anderen, bei denen das Gespräch immer in dieselbe Richtung driftete: “Wir sollten das in Services aufteilen.” Services waren der Beweis, dass die Codebase Modern war. Kubernetes war der Beweis, dass das Infra-Team Modern war. Ein Dutzend Repos mit pyproject.toml-Dateien war der Beweis, dass die Engineering-Praxis Modern war.

Wir haben es getan. Die meisten dieser Extraktionen wurden geshipt. Einige haben sogar funktioniert.

Hier kommt der Teil, den niemand laut sagt: Was die meisten dieser Services “funktionieren” ließ, war, dass sie eng mit der ursprünglichen Datenbank gekoppelt blieben. Die Service-Grenzen waren Illusionen. Die Transaktionsgrenzen waren es nicht. Wenn wir Änderungen über die neuen Services hinweg koordinieren mussten — was ungefähr wöchentlich passierte, weil unsere Domain-Grenzen nicht mit unseren Service-Grenzen übereinstimmten — machten wir das mit Cron-Jobs, Slack-Koordination und Hoffnung.

Die Re-Merge-Ära

Ab 2020 bei Bytro kehrte das Muster um. Wir starteten mit einem Event-Driven- Legacy-System, das in plausibel aussehende Services aufgeteilt worden war, und zogen sie still in ein einzelnes Deployable zurück, das wir — je nach Audience — nannten:

  • “Modularer Monolith” in Design-Docs.
  • “Das große Ding” in Engineering-Meetings.
  • “Eine vernünftige Architektur, über die niemand Conference-Talks hält” im Privaten.

Das war kein Downgrade. Die Dinge, die wir zurückbekamen:

  • Single Deployment. Eine CI-Pipeline. Ein Rollback. Ein Version-Skew-Problem zum Nachdenken bei einem Incident.
  • Refaktorierbare Grenzen. Willst du eine Funktion von Modul A nach Modul B verschieben? Das ist eine IDE-Aktion, kein RFC.
  • Ehrliche Transaktionen. Wenn zwei Operationen atomar sein mussten, konnten sie es. Wenn nicht, haben wir sie trotzdem an der Modulgrenze getrennt — aber haben nicht über die Atomaritätsgarantie gelogen, um eine Deploy-Story zu kaufen.
  • Schnellere lokale Entwicklung. docker compose up öffnet einen Prozess, nicht neun.
  • Günstigere Observability. Die Traces eines Services sind ein Baum. Die Traces von neun Services sind ein Graph — und der Graph lügt dich über die Hälfte der Edges an, weil die Hälfte deiner Spans den Trace-Kontext nicht ordentlich propagiert hat.

Was dir niemand über Microservices sagt

Der Grund, warum Microservices geshipt werden, ist organisatorisch, nicht technisch. Sie ermöglichen Teams, unabhängig zu deployen. Das ist das ganze Pitch, und es ist gut — wenn du mehr als ein Team hast, dessen Deploy-Kadenz tatsächlich in Konflikt steht.

Wenn du drei Teams hast, die an denselben Tagen in derselben Reihenfolge in dieselbe Staging-Umgebung deployen, hast du kein Deployment-Coupling-Problem. Du hast drei Teams und einen Monolithen — und das ist eine funktionierende Anordnung.

Die Pathologie, die ich bei jetzt drei Unternehmen beobachtet habe:

  1. Ein Acht-Personen-Team baut einen Monolithen.
  2. Es beginnt, Reibung zu spüren.
  3. Jemand liest einen Post darüber, wie Netflix Microservices macht.
  4. Sie verbringen 18 Monate damit, Services zu extrahieren.
  5. Am Ende haben sie acht Personen und 14 Services.
  6. Jeder Service gehört niemandem besonders.
  7. Jede Änderung erfordert noch immer Koordination — jetzt aber über Service-Grenzen hinweg, mit Eventual Consistency, mit Distributed Tracing, mit einem brandneuen Failure-Mode namens “Service X ist down, aber Service Y ist up — und was soll Service Z machen?”

Die Reibung, die sie in Schritt 2 spürten, war kein Service-Boundary-Problem. Es war ein Internal-Module-Boundary-Problem. Das kann man beheben, ohne ein Netzwerk einzuführen.

Was “modular” eigentlich bedeutet

Das “modular” im “modularen Monolith” leistet Arbeit. Die Disziplin sieht so aus:

  • Modulgrenzen vom Build-System durchgesetzt, nicht von Konvention. Wenn orders zur Compile-Zeit aus billing importieren kann, hast du keine Grenze. Du hast einen Wunsch. Verwende, was deine Sprache unterstützt: Gos internal- Packages, Rusts Crates, TypeScripts Project References, Javas Modulsystem. Wähle eins, setze es durch, brich den Build bei Verletzungen.
  • Modulgrenzen spiegeln Domain-Grenzen. Das ist das Schwierige. Du bekommst sie beim ersten Mal nicht richtig hin. Du refaktorierst sie zweimal, dann zeigt sich die richtige Form. Der Umstand, dass das Refaktorieren innerhalb eines Monolithen günstig ist, ist genau der Grund, warum er der richtige Ort dafür ist.
  • Ein Pakt darüber, was Grenzen überschreitet. Werte, niemals Referenzen. Events, keine Methodenaufrufe. Published Types, keine internen Types. Sobald das vorhanden ist, ist das spätere Extrahieren eines Moduls zu einem Service ein Wochenend-Job. Ohne das ist die Extraktion ein 18-Monats-Projekt, das einen verteilten Monolithen produziert.

Wann man tatsächlich einen Service auslagert

Ich bin nicht anti-Service. Ich habe Dutzende ausgelagert. Die Signale, nach denen ich jetzt Ausschau halte, sind alle organisatorisch oder operationell — nicht technisch:

  • Ein bestimmtes Team will auf einer anderen Kadenz deployen, jetzt — nicht theoretisch in einem Jahr.
  • Ein bestimmter Workload hat ein Runtime- oder Scaling-Profil, das der Monolith nicht günstig bedienen kann — z.B. ein CPU-hungriger ML-Inference-Pfad.
  • Eine bestimmte Capability hat eine andere Compliance- oder Tenancy-Grenze — z.B. eine PII-intensive Oberfläche, die ihren eigenen Audit-Trail braucht.
  • Eine Vendor-Integration ist naturgemäß ein Service — z.B. ein Webhook- Receiver, der während eines Monolith-Deploys weiterlaufen muss.

Der gemeinsame Faktor: Jedes davon ist ein realer, benannter, konkreter Druck. “Um Modern zu sein” ist nicht auf dieser Liste und wird es nie sein.

Die Belege

Das ist der Teil, wo ich normalerweise eine spezifische p99-Zahl oder eine quantifizierte Deploy-Time-Verbesserung anführen würde. Stattdessen führe ich das an, was mich am meisten überzeugt hat:

Bei Bytro, nachdem wir eine vorher übermäßig fragmentierte Service-Topologie zurück in einen engen modularen Monolithen konsolidiert hatten, wurde die On-Call-Rotation des Teams ruhiger. Nicht weil wir weniger Incidents hatten — wir hatten ungefähr gleich viele. Aber ein “ein Service hat eine Page”-Incident ist ein Ein-Engineer-, Ein-Terminal-, Ein-Rollback-Problem. Ein “sechs Services sind über ihren gegenseitigen State verwirrt”-Incident ist ein Sechs-Engineer- Conference-Call-Problem — und den Schlaf, den du dabei verlierst, bekommst du nicht zurück.

Die On-Call-Rotation ist die Wahrheitsträgerin deiner Architektur. Wenn deine Rotation gesund ist, funktioniert die Architektur. Wenn deine Rotation eine Volkssteuer ist, ist die Architektur falsch — unabhängig davon, welche Form auf dem Whiteboard gezeichnet ist.

Das moderne Argument

Ich gestehe die stille Wahrheit: 2025 und 2026 ist der Monolith + Boring-Tech- Stack der, auf dem ich die aufregendsten Dinge shippen würde. KI-gestützte Entwicklung ist dramatisch einfacher, wenn die gesamte Codebase in das Kontextfenster eines Agents passt. Ein modularer Monolith passt. Neun Services mit vier verschiedenen Sprachen und sechzehn Config-Formaten nicht.

Wenn du willst, dass deine KI-Tools nützlich sind, gib ihnen eine Codebase, die sie lesen können. Das 2026-Argument für den Monolithen ist — unerwartet — dass er die schnellste Architektur für einen LLM-getriebenen Engineering-Workflow ist. Was irgendwie der 2026-codierte Satz ist, den ich je geschrieben habe.