Event-driven-Architektur mit Kotlin: Warum das nicht nur ein modischer Trend ist
\nStellen Sie sich vor, Ihre Anwendung ist ein perfekt abgestimmter Mechanismus. Jedes Zahnrad weiß, wann es sich drehen muss, jeder Kolben, wann er sich bewegen muss. Aber was, wenn eines Tages ein neues Teil hinzugefügt werden muss, ohne die gesamte Maschine anzuhalten? In einer traditionellen synchronen Architektur wird dies oft zum Albtraum: Umschreiben ganzer Module, Testen endloser Aufrufketten und schlaflose Nächte.
\nHier kommt die Event-driven-Architektur mit Kotlin ins Spiel – ein Paradigma, bei dem Komponenten über Ereignisse kommunizieren und nicht über direkte Aufrufe. Kotlin bietet mit seinen Coroutinen und reaktiven Streams einfach ideale Werkzeuge dafür. In diesem Artikel werden wir nicht nur über die Theorie sprechen – wir schreiben echten Code, analysieren Kotlin Flow und Channels, und ich zeige Ihnen, wie Sie ein flexibles, skalierbares System aufbauen, das bei der ersten Belastung nicht auseinanderfällt.
\n\nWas ist eine ereignisgesteuerte Architektur einfach erklärt
\nBeim klassischen Ansatz (stellen Sie sich Microservices vor, die sich gegenseitig per REST anrufen) ist jede Anfrage eine starre Verbindung. Dienst A ruft Dienst B auf, wartet auf eine Antwort, und wenn B ausfällt, bricht alles zusammen. In einer Event-driven-Architektur ist alles anders: Komponenten veröffentlichen Ereignisse (z. B. "Bestellung erstellt") und abonnieren diese. Niemand wartet auf jemanden, niemand ruft direkt an.
\nDas Schöne an Kotlin ist, dass es ermöglicht, dieses Modell nativ zu implementieren, ohne umfangreiche Frameworks einbinden zu müssen. Coroutinen bieten leichtgewichtige Threads, und Channel und Flow sind fertige Busse für Ereignisse.
\nSchauen wir uns ein einfaches Beispiel an – einen eigenen EventBus in Kotlin:
\n\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.channels.*\n\nclass SimpleEventBus {\n private val events = Channel<Any>(Channel.UNLIMITED)\n\n suspend fun publish(event: Any) {\n events.send(event)\n }\n\n suspend fun subscribe(handler: suspend (Any) -> Unit) {\n for (event in events) {\n handler(event)\n }\n }\n}\n\n// Verwendung\nfun main() = runBlocking {\n val bus = SimpleEventBus()\n \n launch {\n bus.subscribe { event ->\n println("Ereignis empfangen: $event")\n }\n }\n \n delay(100)\n bus.publish("Hallo, Event-Driven!")\n delay(100)\n}\n\nDieser Code ist die Basis. Wir erstellen einen Kanal, der wie eine asynchrone Warteschlange funktioniert. Der Herausgeber legt ein Ereignis ab, der Abonnent holt es ab. Alles einfach, aber hier zeigt sich bereits die Stärke der Event-driven-Architektur mit Kotlin: Abonnent und Herausgeber wissen nichts voneinander.
\n\nKotlin Flow: Reaktive Streams für Ereignisse
\nChannel ist großartig, aber für reale Projekte wird oft mehr benötigt: kalte und heiße Streams, Transformationen, Fehlerbehandlung. Hier kommt Kotlin Flow ins Spiel – eine reaktive Bibliothek, die direkt in die Sprache integriert ist.
\nFlow eignet sich ideal für die asynchrone Programmierung mit Kotlin, wenn Ereignisse kontinuierlich generiert werden. Zum Beispiel Wechselkursaktualisierungen, Nachrichtenfeeds oder UI-Zustände.
\n\nKalte vs. heiße Streams
\nEs ist wichtig, den Unterschied zu verstehen:
\n- \n
- Kalte Streams (Cold Stream) – beginnen nur zu arbeiten, wenn jemand sie abonniert hat. Jeder Abonnent erhält seinen eigenen Datenstrom. \n
- Heiße Streams (Hot Stream) – arbeiten ständig, unabhängig von Abonnenten. Späte Abonnenten verpassen alte Ereignisse. \n
Für eine Event-driven-Architektur werden in der Regel heiße Streams benötigt. In Kotlin werden diese über SharedFlow oder StateFlow implementiert.
Beispiel – ein Benachrichtigungssystem:
\n\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.flow.*\n\ndata class Notification(val userId: String, val message: String)\n\nclass NotificationService {\n private val _notifications = MutableSharedF