Die kognitive Last als Showstopper für Reactive Programming


Januar 2025
Unternehmen stehen vor der Herausforderung, ihre Systeme kontinuierlich zu modernisieren, effizienter zu gestalten und zukunftssicher auszurichten. Ein vielversprechender Ansatz ist die reaktive Programmierung, die auch in der Java-Welt durch Project Reactor zum Einsatz kommt. Im Gegensatz zur imperativen Programmierung beschreibt die reaktive Programmierung, was (deklarativ) passieren soll, indem sie auf Datenströme und Ereignisse reagiert, und nicht wie (imperativ) ein Problem durch sequenzielle Anweisungen gelöst wird. Reaktive Programmierung bietet sich besonders an, um asynchrone Datenströme und ereignisgesteuerte Verarbeitung effizient zu handhaben. Doch unsere Erfahrung zeigt: Der Einsatz reaktiver Programmierung muss wohlüberlegt sein – insbesondere dann, wenn die kognitive Belastung der Entwicklerteams die potenziellen Vorteile übersteigt.
Die Stärken reaktiver Programmierung
Reaktive Programmierung eignet sich besonders für Anwendungen mit hoher Parallelität, dynamischen Lastspitzen und der Verarbeitung von Echtzeit-Daten. Auf dem Papier sind die Vorteile klar ersichtlich:
- Höhere Parallelisierung: Durch asynchrone Verarbeitung lassen sich zahlreiche Anfragen simultan bearbeiten. Dies ermöglicht einen höheren Durchsatz und verbessert die Reaktionsfähigkeit der Anwendung, insbesondere unter hoher Last.
- Effizientere Ressourcennutzung: Nicht-blockierende I/O-Operationen reduzieren den Bedarf an Threads, wodurch CPU und Speicher besser ausgelastet werden.
- Geringere Betriebskosten: Durch optimierte Hardwareauslastung können Betriebskosten gesenkt werden.
Doch selbst wenn ein Anwendungsfall grundsätzlich geeignet erscheint, gibt es gewichtige Argumente gegen den Einsatz reaktiver Programmierung.
Die kognitive Last als zentrale Herausforderung
Der größte Trade-off beim Einsatz reaktiver Programmierung ist die erhöhte kognitive Belastung der Entwicklerteams. Die Relevanz der kognitiven Last wird besonders in dem Buch Team Topologies umfassend diskutiert. Gründe für eine erhöhte kognitive Last im Zusammenhang mit reaktiver Programmierung sind:
- Längere Einarbeitungszeit: Die Lernkurve für reaktive Programmierung ist flach. Laut dem Thoughtworks Technology Radar und Erfahrungen aus der Community dauert die Einarbeitung nicht selten 3–6 Monate. Entwickler müssen neue Konzepte wie Backpressure verstehen und Debugging- sowie Testmethoden neu erlernen.
- Verzicht auf essenzieller Control-Flow Features: Gewohnte imperativ-prozedurale Elemente wie einfache Schleifen (for, while, do-while) oder bedingte Anweisungen (if-then, if-then-else, switch) sind in reaktiven Pipelines nur eingeschränkt oder gar nicht nutzbar.
- In vielen Architekturen werden reaktive und imperative Ansätze parallel eingesetzt. Der ständige Wechsel zwischen beiden Programmierparadigmen führt zu einer zusätzlichen Belastung, insbesondere bei geringer Erfahrung.
- Höherer Aufwand für Testen und Debugging: Fehler in reaktiven Systemen sind oft schwer reproduzierbar, und Stack-Traces liefern weniger aussagekräftige Informationen.
Diese erhöhte kognitive Last kann schnell zu steigenden Entwicklungskosten führen, die die betriebswirtschaftlichen Einsparungen durch geringere Betriebskosten wieder aufheben. Obwohl die kognitive Last nur schwer messbar ist, sollte sie so gestaltet werden, dass ein Team sich auf die wesentlichen Aufgaben innerhalb seiner Subdomäne konzentrieren kann. Wenn jedoch ein Framework oder ein bestimmtes Programmierparadigma die Implementierung der Domänenlogik erschwert, beeinträchtigt dies womöglich die Kernaufgabe des Teams – die effiziente und zielgerichtete Umsetzung der Domänenlogik.
Organisatorische Auswirkungen
Besonders kritisch ist der Einsatz reaktiver Programmierung in modulithischen Architekturen. Hier führt die Integration reaktiver Ansätze häufig zu technischen und organisatorischen Konflikten zwischen Teams, da Technologiegrenzen nicht eindeutig gezogen sind. Ein Beispiel dafür ist Spring Boot: Hier können herkömmliche (web-mvc) und reaktive (webflux) REST-Endpunkte nicht parallel genutzt werden.
Wir empfehlen daher den Einsatz reaktiver Programmierung vor allem in Microservice-Architekturen. Dort sind Technologie- und Teamgrenzen klar definiert, sodass einzelne Teams reaktive Ansätze unabhängig nutzen können.
Virtuelle Threads mit Project Loom als Alternative
Mit Java 21 und den virtuellen Threads aus Project Loom gibt es eine vielversprechende Alternative. Virtuelle Threads ermöglichen eine imperative Programmierung mit hoher Parallelität und umgehen so die ineffiziente Hardwarenutzung früherer Java-Versionen. Brian Goetz, seinerseits Java Language Architect bei Oracle, hat es in einem Interview wie folgt formuliert: “I think Loom is going to kill reactive programming … reactive programming was an transitional technology …” (see: Interview auf Youtube).
Für einen detaillierten Benchmarkvergleich können wir dieses GitHub-Repository empfehlen: loom-webflux-benchmarks. Dazu sei erwähnt, dass die Messwerte stark vom Use Case abhängen.
Fazit: Wann reaktive Programmierung sinnvoll ist
Bevor reaktive Programmierung in einer Java-Anwendung eingeführt wird, sollten aus unserer Sicht zwei zentrale Fragen geklärt werden:
- Könnten virtuelle Threads eine effizientere Alternative sein?
- Steht der erwartete Nutzen in einem sinnvollen Verhältnis zur erhöhten kognitiven Last?
Reaktive Programmierung kann in folgenden Szenarien eine sinnvolle Lösung sein:
- Wenn durch eine optimierte Ressourcennutzung signifikante Einsparpotenziale bei den Betriebskosten entstehen – und diese Einsparungen die erhöhte kognitive Last der Teams rechtfertigen.
- Wenn die Anwendung nicht mehr durch zusätzliche Hardware skaliert werden kann.
- Wenn trotz Hardware-Skalierung nicht-funktionale Anforderungen wie Antwortzeiten oder Durchsatz nicht eingehalten werden können.
Reaktive Programmierung ist ein mächtiges Werkzeug, doch ihre Vorteile entfalten sich nur dann, wenn die Teams über die notwendigen Kompetenzen verfügen und die kognitive Last tragbar bleibt. Eine vorschnelle Einführung kann schnell mehr Probleme schaffen, als sie zu lösen verspricht.