ADR-0003: Kafka UND RabbitMQ - zwei Messaging-Systeme mit unterschiedlichem Zweck
Status
Akzeptiert (2026-08-04)
Kontext
Fuer asynchrone Kommunikation zwischen Services stehen grundsaetzlich zwei sehr unterschiedliche Kommunikationsmuster zur Debatte:
- Events ("etwas ist passiert", z. B.
LoanCreated): potenziell interessant fuer mehrere, einander unbekannte Konsumenten; sollen wiederholt gelesen werden koennen (Replay, neue Konsumenten steigen spaeter ein); Reihenfolge pro Aggregat ist wichtig. - Commands ("tu dies", z. B.
SendNotificationCommand): hat genau einen fachlichen Empfaenger; soll genau einmal ausgefuehrt werden; nicht dafuer gedacht, dass ein neuer Konsument spaeter alte Commands nachverarbeitet.
Ein einzelnes Messaging-System fuer beide Faelle zu verwenden ist technisch moeglich (Kafka kann auch als Task-Queue missbraucht werden, RabbitMQ kann auch als Event-Log missbraucht werden), zwingt aber zu Kompromissen bei den jeweils dafuer nicht optimierten Eigenschaften.
Entscheidung
Wir verwenden beide Systeme, je nach Kommunikationsart (siehe common-events):
| Kafka | RabbitMQ | |
|---|---|---|
| Verwendung | Domain Events (IntegrationEvent) |
Commands (IntegrationCommand) |
| Zustellung | Durables Log, 1:N, Consumer Groups | Work Queue, 1:1 pro Nachricht ("competing consumers") |
| Replay | Ja (Offset zuruecksetzen) | Nein (Nachricht ist nach ACK weg) |
| Reihenfolge | Pro Partition/Aggregat garantiert | Pro Queue meist FIFO, aber kein Aggregat-Konzept |
| Fehlerbehandlung | Konsument entscheidet (Retry, Skip, Dead-Letter-Topic) | Native Dead-Letter-Queues, Retry-Policies |
| Beispiel-Topic/-Queue | library.lending.loan-created |
library.notification.send-notification |
notification-service fungiert dabei bewusst als Event-zu-Command-Uebersetzer: es
konsumiert Kafka-Domain-Events und erzeugt daraus konkrete RabbitMQ-Commands
(SendNotificationCommand) - ein haeufiges reales Muster, wenn "was ist passiert" von "was ist
zu tun" entkoppelt werden soll.
Konsequenzen
Positiv
- Jedes System wird fuer den Zweck genutzt, fuer den es entworfen wurde - kein Missbrauch.
- Sehr guter Lerneffekt: Event- vs. Command-Semantik ist eine der wichtigsten Unterscheidungen in event-getriebenen Architekturen und wird hier an echtem Code sichtbar statt nur erklaert.
- Dead-Letter-Queues (siehe
LibraryQueues.SEND_NOTIFICATION_DLQ) demonstrieren robuste Fehlerbehandlung bei Nachrichtenverarbeitung.
Negativ / Trade-offs
- Zwei Broker-Technologien zu betreiben verdoppelt den Infrastruktur- und Lernaufwand gegenueber "nur Kafka" oder "nur RabbitMQ".
- Entwickler muessen zwei Client-Bibliotheken (
spring-kafka,spring-amqp) und zwei Betriebsmodelle verstehen.
Alternativen erwogen
- Nur Kafka fuer alles (auch Commands) - moeglich, aber Kafka-Consumer-Groups bilden "genau einmal von genau einem Worker" nur ueber Umwege ab (eine Partition = ein aktiver Consumer in der Gruppe) und haben kein natives Dead-Letter-Queue-Konzept.
- Nur RabbitMQ fuer alles (auch Events) - moeglich (Fanout-Exchange fuer Pub/Sub), aber ohne Replay/Log-Semantik verliert man die Faehigkeit, dass ein neuer Konsument (z. B. ein spaeter hinzugefuegtes Reporting/Analytics-System) die komplette Event-Historie neu einlesen kann.