FrĂĽher war es einfach: Ihre gesamte Anwendung lief in einem einzigen Prozess. Eine Codebasis, ein Deployment, alles im selben Speicher. Der Monolith. Nicht sexy, aber handhabbar.
Microservices versprachen es besser zu machen. Separate Services. Unabhängige Deployments. API-Gateways. Kubernetes. Service Mesh. Alles ordentlich aufgeteilt. Teams autonom.
Bis man sich das Laufzeitverhalten ansieht.
Dann stellt man fest, dass eine einfache Benutzeraktion von vierzehn Services, drei Datenbanken, zwei Queues, einem externen Identity Provider und einer Caching-Schicht abhängt, die still den Unterschied zwischen 200ms und 12 Sekunden ausmacht.
Der Monolith ist nicht verschwunden. Er ist jetzt nur ĂĽber das Netzwerk verteilt. Das macht die Kopplung nur schwerer zu sehen.
Die Illusion der Unabhängigkeit
Microservices werden als unabhängig deploybare Komponenten verkauft. In der Theorie stimmt das. Und ja — viele Teams verwenden Message Broker und ereignisgesteuerte Muster, um Services zu entkoppeln. Das hilft.
Aber schaut man sich den Pfad an, den ein Benutzer tatsächlich trifft: den API-Aufruf, der eine Antwort erwartet. Dort ist async selten eine Option. Asynchrones Messaging reduziert die Laufzeitkopplung, entfernt aber keine Geschäftskopplung. Ein Benutzer, der eine Bestellung aufgibt, möchte eine Bestätigung — nicht “wir melden uns später.”
Und genau auf diesen Pfaden sehe ich in nahezu jedem Projekt dasselbe entstehen: Service A wartet synchron auf B für die Autorisierung. B prüft bei C den Preis. C holt den Bestand von D. Währenddessen wartet noch der Benutzer.
Auf dem Papier sind das vier separate Services. Zur Laufzeit ist es eine Kette.
Und Ketten haben eine unangenehme Eigenschaft: Sie sind nur so stark wie ihr schwächstes Glied.
Ein Netzwerkaufruf ist kein Funktionsaufruf
Dieser Unterschied wird strukturell unterschätzt.
Ein Funktionsaufruf innerhalb eines Monolithen ist vorhersehbar: Speicher ist lokal, Latenz ist Mikrosekunden, kaum externe Faktoren. Ein Netzwerkaufruf führt ein völlig anderes Fehlermodell ein: Latenz, Timeouts, Retries, DNS-Probleme, TLS-Handshakes, Connection Pooling, transiente Fehler.
Was lokal Millisekunden kostete, wird unter Last plötzlich Thread-Erschöpfung und kaskadierende Fehler. Nicht weil der Code schlecht ist, sondern weil sich das Netzwerk nicht wie Speicher verhält — und Ihre Architektur so tut, als ob es das täte.
Und das Tückische: Dieser Abbau verläuft selten linear. Er erzeugt Queues, Retries, Ressourcenkonflikte und Gegendruck in jedem System, das davon abhängt. Die Mathematik dahinter ist einfach, aber unerbittlich — aus der Warteschlangentheorie wissen wir, dass Wartezeiten exponentiell mit der Last wachsen. Ein Service bei 50% Auslastung hat eine Wartezeit gleich seiner Verarbeitungszeit. Bei 90% ist es das Neunfache. Bei 95% das Neunzehnfache.
Das bedeutet, ein Service, der 300ms langsamer wird, verursacht nicht 300ms zusätzliche Latenz. Er schiebt sich und alles, was von ihm abhängt, über einen Kipppunkt. Queues füllen sich, Retries häufen sich, Threads erschöpfen sich. Ein langsames Glied kann zehn gesunde Services runterziehen. Und die eigentliche Quelle ist fast nie dort, wo der Alarm ausgeht.
Das ist genau das, worum es bei den Fallacies of Distributed Computing geht. Und doch sehe ich sie jedes Mal wiederkehren, eingehĂĽllt in ein sauberes Architekturdiagramm.
Wie ein verteilter Monolith entsteht
Es kommt selten von schlechten Ingenieuren. Es entsteht, weil Teams Funktionalität entlang logischer Linien aufteilen — Benutzer, Bestellungen, Bestand, Zahlungen, Benachrichtigungen — während die zugrundeliegenden Geschäftsprozesse eng gekoppelt bleiben.
Eine Bestellung aufgeben erfordert immer noch: Authentifizierung, Bestandsprüfung, Preisberechnung, Zahlung und Benachrichtigung. Nur laufen diese Abhängigkeiten jetzt über HTTP oder gRPC statt in-process.
Die Kopplung verschwindet nicht. Sie wird schwerer zu sehen, schwerer zu debuggen und sensibler gegenĂĽber Latenz.
Observability ist kein Luxus mehr
In einem Monolith kann man noch linear debuggen. Ein Stack Trace sagt einem einigermaĂźen gut, wo etwas schiefgeht. Mit Microservices gibt es diesen Luxus nicht.
Eine Fehlermeldung in Service A kann durch ein Timeout in C, langsamen Storage in D, Gegendruck in einer Queue oder eine Abhängigkeit, die “halb kaputt” ist — gerade genug funktioniert, um keinen Alarm auszulösen, aber zu langsam, um das System gesund zu halten — verursacht werden.
Distributed Tracing, Correlation IDs und strukturiertes Logging sind hier nicht optional. Sie sind der Unterschied zwischen “wir verstehen unser eigenes System” und “wir deployen und hoffen das Beste.”
Mehr Services ≠bessere Architektur
Microservices lösen echte Probleme: Team-Autonomie, unabhängige Deployments, gezielte Skalierbarkeit, Fehler-Isolation.
Aber sie führen mindestens genauso viel Komplexität ein: Netzwerkverhalten, Versionierung, Deployment-Koordination, operative Abhängigkeiten und Laufzeitinstabilität.
Dieser Trade-off wird zu selten explizit gemacht. Die Frage sollte nicht lauten “Wie teilen wir die Anwendung auf?”, sondern “Rechtfertigt unsere Skalierung und Teamgröße diese operative Komplexität?” Für viele Organisationen ist die ehrliche Antwort: Nein.
Was man dagegen tun kann
Einen verteilten Monolith zu erkennen ist Schritt eins. Aber ihn zu erkennen ohne zu handeln erzeugt nur Zynismus. Ein paar Muster, die ich in der Praxis effektiv funktionieren sehe:
Mache Abhängigkeiten wo möglich asynchron. Nicht jede Anfrage muss synchron bearbeitet werden. Ereignisgesteuerte Muster mit einem Message Broker entkoppeln Services in der Zeit — und damit in der Verfügbarkeit. Eine Bestellung muss nicht warten, bis die Benachrichtigung gesendet wurde.
Designe fĂĽr Fehler, nicht nur fĂĽr Erfolg. Circuit Breaker, Bulkheads, Timeouts und Fallbacks sind keine Optimierungen. Sie sind grundlegende Architektur in dem Moment, wo man ĂĽber ein Netzwerk kommuniziert.
Wage es, Grenzen neu zu ziehen. Wenn zwei Services immer zusammen deployt werden müssen, immer zusammen scheitern und immer synchron aufeinander warten — dann sind das nicht zwei Services. Dann ist es ein Service mit einem Netzwerkaufruf dazwischen. Ziehe die Konsequenz und füge sie zusammen.
Investiere in Observability, bevor man sie braucht. Distributed Tracing und strukturiertes Logging nachträglich zu einer Landschaft von dreißig Services hinzuzufügen ist ein Albtraum. Beginne mit Service zwei.
Der eigentliche Unterschied
Gute verteilte Architektur zeichnet sich nicht aus, wenn alles funktioniert. Sie zeichnet sich aus, wenn Teile des Systems es nicht mehr tun.
Die Frage ist nicht, ob etwas ausfällt. Bei ausreichender Skalierung fällt immer etwas aus. Die Frage ist, ob das System so designed ist, damit umzugehen — oder ob eine langsame Datenbank die gesamte Landschaft zum Erliegen bringt.
Das ist der Unterschied zwischen Microservices als Architekturentscheidung und Microservices als Hype. Und das ehrliche Gespräch darüber beginnt damit, das Laufzeitverhalten statt Architekturdiagrammen zu betrachten.