Von Request-Response zu Event-Driven
Die meisten Enterprise-Systeme sind historisch als Request-Response-Architekturen gewachsen. Service A ruft Service B auf, wartet auf die Antwort und verarbeitet sie. Das funktioniert, solange die Abhängigkeiten überschaubar sind. Aber in einer Microservices-Landschaft mit dutzenden Services, die miteinander kommunizieren müssen, stößt dieses Modell an seine Grenzen: enge Kopplung, kaskadierende Ausfälle und Schwierigkeiten bei der Skalierung.
Event-Driven Architecture kehrt das Prinzip um. Statt dass Services sich gegenseitig aufrufen, publizieren sie Events, Fakten darüber, was passiert ist. Andere Services reagieren auf diese Events, wenn sie relevant sind. Die Kopplung verschiebt sich von der Laufzeit in die Designzeit.
Warum Kafka statt JMS?
In der Java-Enterprise-Welt war JMS (Java Message Service) lange der Standard für asynchrone Kommunikation. ActiveMQ, IBM MQ, bewährte Technologien. Warum also der Wechsel zu Kafka?
Die Unterschiede sind fundamental:
| Eigenschaft | JMS (klassisch) | Apache Kafka |
|---|---|---|
| Modell | Queue + Topic | Append-only Log |
| Speicherung | Nachricht wird nach Konsumierung gelöscht | Nachricht bleibt erhalten (konfigurierbare Retention) |
| Replay | Nicht möglich | Jederzeit möglich |
| Durchsatz | Tausende Messages/sec | Millionen Messages/sec |
| Consumer | Exklusiv oder Broadcast | Consumer Groups mit paralleler Verarbeitung |
| Ordering | Queue-global | Partition-level |
Der entscheidende Punkt ist das Append-only Log. In Kafka werden Nachrichten nicht gelöscht, nachdem sie konsumiert wurden. Sie bleiben für einen konfigurierbaren Zeitraum erhalten. Das ermöglicht Replay, ein neuer Service kann die gesamte Historie eines Topics nachfahren und sich seinen Zustand selbst aufbauen.
Topics, Partitions und Consumer Groups
Das Verständnis von Kafkas Architektur ist essenziell für einen erfolgreichen Einsatz.
Topics
Ein Topic ist ein logischer Kanal für eine bestimmte Art von Events. Die Granularität der Topics ist eine Designentscheidung: Ein Topic pro Entity-Typ (z.B. billing.invoice.created) hat sich in der Praxis bewährt. Zu grobe Topics führen dazu, dass Consumer irrelevante Nachrichten filtern müssen. Zu feine Topics erschweren die Verwaltung.
Partitions
Jedes Topic wird in Partitions aufgeteilt. Partitions sind die Einheit der Parallelisierung. Nachrichten innerhalb einer Partition sind geordnet, über Partitions hinweg gibt es keine Ordnungsgarantie. Die Wahl des Partition Keys bestimmt, welche Nachrichten in dieselbe Partition gelangen:
ProducerRecord<String, InvoiceEvent> record =
new ProducerRecord<>("billing.invoice",
invoice.getCustomerId(), // Partition Key
event);
producer.send(record);
Mit der Customer-ID als Partition Key landen alle Events eines Kunden in derselben Partition. Damit ist die Reihenfolge pro Kunde garantiert, was für die Abrechnung essenziell ist.
Consumer Groups
Consumer Groups ermöglichen die parallele Verarbeitung eines Topics. Jede Partition wird genau einem Consumer innerhalb einer Group zugewiesen. Mehr Consumer als Partitions bringen keinen Vorteil, die überzähligen Consumer bleiben idle.
Topic: billing.invoice (6 Partitions)
Consumer Group "billing-service":
Consumer 1 → Partition 0, 1
Consumer 2 → Partition 2, 3
Consumer 3 → Partition 4, 5
Consumer Group "analytics-service":
Consumer 1 → Partition 0, 1, 2, 3, 4, 5
Verschiedene Consumer Groups lesen dasselbe Topic unabhängig voneinander. Der Billing-Service und der Analytics-Service verarbeiten dieselben Events, ohne sich gegenseitig zu beeinflussen.
Praxisbeispiel: Billing und Datenverarbeitung
In einem Logistikprojekt wurde Kafka als zentrale Event-Plattform eingeführt. Der Auslöser war ein klassisches Problem: Das Billing-System musste Daten aus mehreren Quellsystemen aggregieren, und die bisherige Batch-Verarbeitung über Nacht war zu langsam geworden.
Die Umstellung auf Event-Driven Architecture brachte mehrere Vorteile:
- Near-Realtime Billing: Statt Batch-Verarbeitung über Nacht werden Abrechnungsdaten in Echtzeit aggregiert
- Entkopplung: Das Billing-System ist nicht mehr von der Verfügbarkeit der Quellsysteme abhängig
- Auditierbarkeit: Jedes Event ist im Log nachvollziehbar, keine verlorenen Nachrichten
- Skalierbarkeit: Durch Partitioning kann der Durchsatz horizontal skaliert werden
Event Sourcing und CQRS
Kafka als Event Log öffnet die Tür zu zwei weiterführenden Architekturmustern:
Event Sourcing
Statt den aktuellen Zustand einer Entität in einer Datenbank zu speichern, werden alle Zustandsänderungen als Events persistiert. Der aktuelle Zustand ergibt sich durch das Abspielen aller Events. Kafka eignet sich als Event Store, sofern die Retention entsprechend konfiguriert ist, idealerweise unbegrenzt für geschäftskritische Topics.
CQRS, Command Query Responsibility Segregation
CQRS trennt das Schreibmodell (Commands) vom Lesemodell (Queries). Events werden geschrieben und von unterschiedlichen Read-Modellen konsumiert, die jeweils für ihren Anwendungsfall optimiert sind. Ein Read-Modell für das Dashboard, ein anderes für Reports, ein drittes für die Suche, jedes konsumiert dieselben Events und baut seine eigene Projektion auf.
Die Kombination von Event Sourcing und CQRS mit Kafka als Infrastruktur ist ein mächtiges Architekturmuster, das allerdings auch Komplexität mitbringt. Eventual Consistency, Idempotenz der Consumer und Schema-Evolution sind Herausforderungen, die bewusst adressiert werden müssen.
Fazit
Apache Kafka ist mehr als ein Messaging-System, es ist eine Plattform für Event-Driven Architecture. Der Wechsel von Request-Response zu Event-Driven verändert nicht nur die technische Architektur, sondern auch die Art, wie Teams über ihre Systeme nachdenken. Events als First-Class Citizens zu behandeln, führt zu loserer Kopplung, besserer Skalierbarkeit und einer natürlichen Auditierbarkeit. Der Einstieg erfordert ein Umdenken, aber die langfristigen Vorteile überwiegen die initiale Lernkurve deutlich.