Event-Driven Architecture mit Apache Kafka
Event-Driven Architecture mit Apache Kafka
Artikel-Übersicht
Apache Kafka ist nach Franz Kafka benannt und hat mit Literatur nichts zu tun — der Name geht auf das Entwicklerteam bei LinkedIn zurück, das die Plattform ursprünglich gebaut hat. Diese Klarstellung gleich zu Beginn, weil Google den Schriftsteller regelmäßig in dieselben Suchergebnisse mischt. Der Artikel trennt danach sauber zwischen dem Muster — ereignisgetriebene Architektur — und der Plattform Kafka, weil beides ständig verwechselt wird. Der zentrale Abschnitt erklärt den Unterschied, aus dem fast alles Weitere folgt: Kafka ist ein persistentes Log, keine Warteschlange. Es folgen ein lauffähiges Beispiel, die gängigen Ereignismuster mit ihren Kosten, ein fairer Vergleich mit RabbitMQ und fünf Fallstricke aus der Praxis. Zum Schluss die ehrliche Frage, wann sich das Muster nicht lohnt.
Was ereignisgetriebene Architektur ist
Bei ereignisgetriebener Architektur teilen Dienste mit, dass etwas geschehen ist, statt einander aufzufordern, etwas zu tun.
Der Unterschied klingt subtil und ändert alles:
| Befehl | Ereignis | |
|---|---|---|
| Formulierung | „Erstelle eine Rechnung" | „Ein Auftrag wurde abgeschlossen" |
| Zeitform | Imperativ | Vergangenheit |
| Empfänger | genau einer, bekannt | beliebig viele, unbekannt |
| Absender weiß | wer es tun soll | nur, dass es passiert ist |
| Neuer Empfänger | Absender muss geändert werden | Absender bleibt unverändert |
Die letzte Zeile ist der eigentliche Gewinn. Wenn die Buchhaltung künftig auch über abgeschlossene Aufträge informiert werden soll, ändert sich beim Vertrieb nichts — sie hört einfach mit. Genau das meint Entkopplung.
Der Preis: Der Absender weiß nicht mehr, ob etwas passiert ist. Bei einem Befehl bekommt man eine Antwort. Bei einem Ereignis muss man die Verarbeitung überwachen, sonst merkt niemand, dass ein Empfänger seit drei Tagen ausgefallen ist.
Was Apache Kafka ist
Kafka ist ein verteiltes, persistentes Log — Ereignisse werden angehängt und bleiben liegen, statt beim Lesen zu verschwinden.
Dieser eine Satz ist der Schlüssel zum ganzen System. Wer Kafka als Warteschlange denkt, wundert sich über fast jedes Verhalten.
Wie Kafka funktioniert
Vier Begriffe genügen für den Anfang:
Topic. Ein benannter Strom von Ereignissen, etwa auftrag.abgeschlossen. Vergleichbar mit einer Tabelle, in die nur angehängt wird.
Partition. Jedes Topic ist in Partitionen aufgeteilt. Innerhalb einer Partition ist die Reihenfolge garantiert, über Partitionen hinweg nicht. Welche Partition ein Ereignis bekommt, entscheidet sein Schlüssel — dieselbe Kundennummer landet immer in derselben Partition und behält damit ihre Reihenfolge.
Offset. Die Position eines Ereignisses in seiner Partition. Der Konsument merkt sich seine Position, nicht der Broker. Daraus folgt die wichtigste Eigenschaft: Man kann zurückspulen und alles noch einmal lesen.
Konsumentengruppe. Mehrere Instanzen desselben Dienstes teilen sich die Partitionen eines Topics. Verschiedene Gruppen lesen unabhängig voneinander — jede hat ihre eigene Position.
Aus diesen vier Begriffen folgt der Rest fast von selbst. Ein neuer Dienst, der auf ein bestehendes Topic hört, bekommt eine eigene Gruppe und kann die Historie von vorn lesen, ohne dass die anderen etwas merken.
Kafka ist keine Warteschlange
| Warteschlange (z. B. RabbitMQ) | Log (Kafka) | |
|---|---|---|
| Nach dem Lesen | Nachricht ist weg | Ereignis bleibt liegen |
| Aufbewahrung | bis zur Verarbeitung | konfigurierte Dauer oder Größe |
| Mehrere Empfänger | über zusätzliche Verteilung | eingebaut, unabhängige Gruppen |
| Wiederholung | nur bei Fehler, aus Warteschlange | jederzeit, durch Zurücksetzen der Position |
| Reihenfolge | je Warteschlange | je Partition |
| Position merkt sich | der Broker | der Konsument |
| Typischer Einsatz | Arbeitsaufträge verteilen | Ereignisströme, mehrere Interessenten |
Die Zeile „Wiederholung" ist der praktische Hauptgewinn: Wenn ein Verarbeitungsfehler eine Woche lang falsche Ergebnisse erzeugt hat, korrigiert man den Code und liest die Woche erneut. Mit einer Warteschlange geht das nicht — die Nachrichten sind weg.
Ein erstes Beispiel
Eine lokale Umgebung mit einem Broker:
services:
kafka:
image: apache/kafka:latest
ports:
- "9092:9092"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1Bemerkenswert daran ist, was fehlt: ZooKeeper. Kafka verwaltet seine Metadaten inzwischen selbst; ältere Anleitungen im Netz zeigen noch den Aufbau mit einem separaten Koordinationsdienst.
Ein Producer und ein Consumer in Python:
from kafka import KafkaProducer, KafkaConsumer
import json
producer = KafkaProducer(
bootstrap_servers="localhost:9092",
key_serializer=lambda k: k.encode(),
value_serializer=lambda v: json.dumps(v).encode(),
)
producer.send(
"auftrag.abgeschlossen",
key="kunde-4711", # bestimmt die Partition
value={"auftragId": "A-2026-0042", "betrag": 149.90},
)
producer.flush()consumer = KafkaConsumer(
"auftrag.abgeschlossen",
bootstrap_servers="localhost:9092",
group_id="rechnungsstellung", # eigene Gruppe, eigene Position
auto_offset_reset="earliest", # beim ersten Start von vorn lesen
value_deserializer=lambda v: json.loads(v.decode()),
)
for nachricht in consumer:
print(nachricht.partition, nachricht.offset, nachricht.value)Der Schlüssel kunde-4711 ist kein Beiwerk: Er sorgt dafür, dass alle Ereignisse dieses Kunden in derselben Partition landen und damit in Reihenfolge verarbeitet werden. Wer keinen Schlüssel setzt, bekommt Gleichverteilung — und verliert jede Reihenfolgegarantie.
Die Ereignismuster
| Muster | Problem | Wann sinnvoll | Kosten |
|---|---|---|---|
| Ereignisbenachrichtigung | „etwas ist passiert", Empfänger holt Details selbst | Standardfall, geringste Kopplung | Rückfrage nötig |
| Event-Carried State Transfer | Ereignis enthält alle nötigen Daten | wenn Rückfragen zu teuer sind | größere Ereignisse, Datenduplikate |
| Outbox | Datenänderung und Veröffentlichung atomar | sobald Ereignisse aus einer Transaktion kommen | Tabelle plus Relay |
| Saga | fachliche Transaktion über Dienstgrenzen | wenn unvermeidbar | hoch, Kompensationslogik |
| CQRS | Lese- und Schreibmodell trennen | bei stark unterschiedlicher Last | zwei Modelle, Synchronisation |
| Event Sourcing | Zustand als Folge von Ereignissen speichern | wenn die Historie fachlich gefordert ist | sehr hoch — eigenes Vorhaben |
Zur letzten Zeile ein deutlicher Hinweis: Event Sourcing ist kein Nebenprodukt von Kafka. Es verlangt Antworten auf Schemaänderungen alter Ereignisse, auf Auswertungen über die Historie, auf Datenschutzlöschungen und auf die Frage, wie man einen Fehler korrigiert, der vor zwei Jahren passiert ist. Wer es einführt, weil es zum Thema zu gehören scheint, hat sich ein zweites Projekt gekauft. Dass auch Domain-Driven Design es nicht voraussetzt, ist dort ausführlich begründet.
Saga und Outbox behandle ich aus der Dienstperspektive in Microservices bauen.
Ereignisse schneiden und benennen
Drei Regeln, die sich bewährt haben:
- Vergangenheitsform.
AuftragAbgeschlossen, nichtAuftragAbschliessen. Der Name entscheidet mit darüber, ob jemand das Ereignis als Befehl missversteht. - Fachliche Namen, keine technischen.
RechnungStorniertsagt etwas,RechnungUpdatednicht. - Schemata versionieren. Ein Ereignis ist eine Schnittstelle mit allen Folgen — dieselben Fragen wie in API-Design, nur dass die Empfänger noch weniger bekannt sind.
Kafka oder RabbitMQ?
| Kafka | RabbitMQ | |
|---|---|---|
| Modell | persistentes Log | Warteschlange mit Vermittlung |
| Stärke | Durchsatz, Wiederholbarkeit, viele Interessenten | flexible Verteilung, Prioritäten, Verzögerung |
| Betrieb | aufwendiger, verteiltes System | leichter, ein Dienst genügt oft |
| Reihenfolge | je Partition | je Warteschlange |
| Gut für | Ereignisströme, Auswertungen, Wiederaufbereitung | Arbeitsaufträge, Aufgabenverteilung |
Die ehrliche Empfehlung: Für Aufgabenverteilung ist RabbitMQ einfacher und völlig ausreichend. Für Ereignisströme mit mehreren unabhängigen Konsumenten und der Möglichkeit, Vergangenes erneut zu lesen, ist Kafka richtig.
Wer Kafka einführt, weil es der bekanntere Name ist, handelt sich einen erheblichen Betriebsaufwand ein — und merkt es meist erst beim ersten Vorfall.
Die fünf Fallstricke
1. Reihenfolge gilt nur je Partition. Symptom: Ereignisse desselben Kunden werden in falscher Reihenfolge verarbeitet. Ursache: kein oder falscher Schlüssel. Gegenmaßnahme: Als Schlüssel die fachliche Entität wählen, deren Reihenfolge zählt.
2. Genau-einmal-Zustellung ist teuer und selten nötig. Symptom: jemand baut komplizierte Transaktionslogik, um Duplikate zu vermeiden. Gegenmaßnahme: Konsumenten idempotent bauen. Das ist einfacher, robuster und funktioniert auch, wenn die Zusage einmal nicht greift.
3. Schemaänderungen brechen Konsumenten still. Symptom: Ein Feld wurde umbenannt, ein Konsument verarbeitet seitdem falsche Daten, ohne zu scheitern. Gegenmaßnahme: Eine Schemaverwaltung mit Kompatibilitätsprüfung, oder mindestens ein verbindlicher Prozess für Ereignisänderungen.
4. Die Aufbewahrungsdauer ist eine fachliche Entscheidung. Symptom: Ein neuer Dienst soll die Historie lesen — sie ist seit sieben Tagen gelöscht. Gegenmaßnahme: Vor dem ersten Topic klären, wie lange Ereignisse verfügbar bleiben müssen, und das dokumentieren.
5. Kafka ist ein verteiltes System mit eigenem Betriebsaufwand. Symptom: Der Cluster braucht Aufmerksamkeit, die niemand eingeplant hat. Gegenmaßnahme: Entweder Betriebskompetenz aufbauen oder ein verwaltetes Angebot nutzen — für den DACH-Raum gibt es Angebote mit Rechenzentren in der EU und der Schweiz, was für viele Projekte das Auswahlkriterium ist.
Wann das Muster nicht lohnt
- Wenige Dienste. Bei drei Diensten ist ein synchroner Aufruf nachvollziehbarer als ein Ereignis, das irgendwo verarbeitet wird.
- Keine Betriebskompetenz. Ein Ereignisstrom, den niemand überwacht, ist schlechter als ein Aufruf, der sichtbar fehlschlägt.
- Wenn eine Antwort gebraucht wird. Ereignisse sind einseitig. Wer auf ein Ergebnis warten muss, braucht einen synchronen Aufruf.
Ob die Verteilung überhaupt die richtige Entscheidung ist, behandelt Microservices oder Monolith.
Und ein Anwendungsfall aus dem KI-Umfeld, weil er das Muster gut zeigt: Wenn sich Dokumente ändern, muss ein Suchindex nachgezogen werden. Ein Ereignisstrom dokument.geaendert, auf den ein Indexierungsdienst hört, ist dafür die natürliche Form — der Dienst, der das Dokument ändert, muss nichts über die Indexierung wissen. Wie so eine Indexierung aussieht, steht in RAG selbst bauen.
Häufige Fragen
Was ist Apache Kafka? Ein verteiltes, persistentes Log für Ereignisströme. Ereignisse werden angehängt und bleiben für eine konfigurierte Dauer liegen, statt beim Lesen zu verschwinden.
Warum heißt Apache Kafka so? Der Name wurde vom Entwicklerteam bei LinkedIn gewählt, weil das System für das Schreiben optimiert ist — eine Anspielung auf den Schriftsteller, ohne inhaltlichen Bezug.
Wie funktioniert Apache Kafka? Über Topics, die in Partitionen aufgeteilt sind. Der Schlüssel eines Ereignisses bestimmt die Partition, die Position merkt sich der Konsument. Daraus folgen Reihenfolgegarantie je Partition und die Möglichkeit, erneut zu lesen.
Was ist ereignisgetriebene Architektur? Ein Entwurfsansatz, bei dem Dienste mitteilen, dass etwas geschehen ist, statt einander aufzufordern, etwas zu tun. Das entkoppelt Absender und Empfänger.
Kafka oder RabbitMQ? RabbitMQ für Aufgabenverteilung, Kafka für Ereignisströme mit mehreren unabhängigen Konsumenten und der Möglichkeit, Vergangenes erneut zu verarbeiten.
Braucht Kafka noch ZooKeeper? Nein. Kafka verwaltet seine Metadaten inzwischen selbst; ältere Anleitungen zeigen noch den Aufbau mit separatem Koordinationsdienst.
Zusammenfassung und praktische Tipps
Ereignisgetriebene Architektur entkoppelt und verschiebt die Komplexität in die Fehlerbehandlung. Die wichtigsten Punkte:
- Kafka ist ein Log, keine Warteschlange. Aus diesem einen Unterschied folgt fast alles.
- Ereignisse sind Vergangenheit, Befehle sind Imperativ. Der Name entscheidet mit über die Kopplung.
- Der Schlüssel bestimmt die Partition — und damit, ob die Reihenfolge stimmt.
- Konsumenten idempotent bauen ist einfacher als jede Zustellgarantie.
- Event Sourcing ist ein eigenes Vorhaben, kein Nebenprodukt.
- Für Aufgabenverteilung ist RabbitMQ die einfachere Wahl.
- Die Aufbewahrungsdauer ist eine fachliche Entscheidung und sollte vor dem ersten Topic feststehen.
Mein Rat für den Einstieg: Bau das Beispiel oben lokal auf und setz einmal bewusst die Konsumentenposition zurück, um denselben Strom noch einmal zu lesen. Dieser eine Handgriff macht den Unterschied zwischen Log und Warteschlange greifbarer als jede Erklärung — und er zeigt dir sofort, ob dein Konsument idempotent ist.
Ressourcen
- Apache Kafka – Dokumentation – die Referenz, ausführlich und aktuell
- Kafka – Design – warum das Log-Modell gewählt wurde
- Event-Driven Architecture – Martin Fowler – die Unterscheidung der Ereignismuster
- RabbitMQ – Tutorials – für den Vergleich mit dem Warteschlangenmodell