Zurück zu den Artikeln
📄
event-driven architectureasynchronous processingmessage queuesevent sourcingdecouplingevent replayload levelingaudit trail

Ereignisgesteuerte Mechanismen: Welche Probleme sie lösen und wann sie passen

Ereignisgesteuerte Architekturen lösen harte Kopplungen auf, ermöglichen asynchrone Fan-Out-Muster und sichern Hochverfügbarkeit unter Spitzenlasten. Erfahren Sie, wann Message Broker wie Kafka oder RabbitMQ unverzichtbar sind, welche Stolperfallen bei Outbox-Patterns und Idempotenz lauern und wann synchrone Aufrufe die bessere Wahl bleiben.

Ereignisgesteuerte Mechanismen: Welche Probleme sie lösen und wann sie passen

Ereignisgesteuerte Architektur (EDA) ist besonders vorteilhaft, wenn ein System auf Änderungen reagieren muss, ohne dass jede Komponente jede andere direkt aufruft. Dieser Ansatz kann die Kopplung senken, führt jedoch auch zu Herausforderungen wie Warteschlangen, eventual consistency und einer komplexeren Fehlersuche, weil die Nachrichten asynchron übermittelt werden und damit Zustellung, Reihenfolge und Fehlerbehandlung explizit geregelt werden müssen. Daher entscheidet der konkrete Anwendungsfall, ob die Vorteile die zusätzlichen Komplexitäten aufwiegen.

Die Grundidee

Im Request‑Response‑Design ruft eine Komponente eine andere auf und wartet auf die Rückmeldung. Im Gegensatz dazu veröffentlicht eine Komponente im ereignisgesteuerten Design ein Faktum – etwa OrderPlaced – und interessierte Abonnenten können dieses Ereignis eigenständig empfangen und verarbeiten.

Traditionell:
OrderService → ruft auf → InventoryService
OrderService → ruft auf → EmailService
OrderService → ruft auf → AnalyticsService

Ereignisgesteuert:
OrderService → sendet → OrderPlaced
                           ↓
              InventoryService (abonniert)
              EmailService (abonniert)
              AnalyticsService (abonniert)

In diesem Modell muss der Veröffentlicher nicht jeden Abonnentin kennen, was ein erheblicher Vorteil ist. Die Entkopplung verringert die direkte Abhängigkeit, beseitigt jedoch nicht die Notwendigkeit einer zuverlässigen Zustellung, einer korrekten Reihenfolge und eines wirksamen Fehlerschutzes. Ohne diese Garantien könnten nachgelagerte Dienste unvollständige oder veraltete Daten erhalten, was die Geschäftslogik gefährdet.

Welche Probleme ereignisgesteuerte Mechanismen lösen können

1. Enge Kopplung zwischen Services

Ein direkter Aufrufer hängt von der Schnittstelle und Verfügbarkeit des aufgerufenen Dienstes ab, was enge Kopplungen erzeugt. Ein ereignisgesteuerter Ansatz erlaubt dem Veröffentlicher, seine Arbeit abzuschließen, während die Abonnenten die Ereignisse eigenständig verarbeiten. So kann das System weiterarbeiten, selbst wenn ein Konsument temporär ausfällt.

class OrderService {
  constructor(private eventBus: EventBus) {}

  placeOrder(order: Order) {
    this.eventBus.emit('order.placed', order);
  }
}

Dieses Beispiel verdeutlicht das Grundkonzept, vernachlässigt jedoch wichtige Aspekte wie dauerhafte Veröffentlichung, Autorisierung, Wiederholungsversuche und transaktionale Konsistenz. Ein rein in‑Memory‑emit ist nicht ausreichend für einen zuverlässigen Message‑Broker in Produktionsumgebungen, weil ein solcher Broker Ereignisse über Neustarts hinweg persistieren und Zugriffskontrollen durchsetzen muss.

2. Fan‑out

Mehrere Abonnenten können auf ein Ereignis reagieren, ohne dass der Veröffentlicher eine neue direkte Abhängigkeit hinzufügen muss.

eventBus.on('order.placed', async (order) => {
  await loyaltyService.awardPoints(order);
});

Auf diese Weise können mehrere Dienste auf dasselbe Ereignis reagieren, ohne die Komplexität des ursprünglichen Systems zu erhöhen.

3. Zeitliche Entkopplung

Eine dauerhafte Warteschlange kann ein Ereignis behalten, solange ein Abonnent nicht verfügbar ist. Der ursprüngliche Vorgang kann erfolgreich abgeschlossen werden, und der Abonnent kann später nachholen – vorausgesetzt, der Geschäftsprozess toleriert eventual consistency. Damit lässt sich ein temporäres Versagen eines Dienstes abfedern, ohne die Kernfunktion zu unterbrechen.

OrderService → Queue → [EmailService offline]
                    ↓
              [EmailService wiederhergestellt] → verarbeitet die anstehenden Ereignisse

Dieser Mechanismus ermöglicht es Systemen, vorübergehende Ausfälle elegant zu handhaben und sicherzustellen, dass kritische Vorgänge nicht wegen temporärer Probleme scheitern.

4. Lastglättung

Eine Warteschlange kann einen temporären Anstieg der Aktivität aufnehmen und den Abonnenten erlauben, den Arbeitsaufwand in einem nachhaltigen Tempo zu verarbeiten. Sie beseitigt jedoch nicht die Kapazitätsgrenzen; Tiefe, Alter und Fehlerrate der Warteschlange müssen überwacht werden, um optimale Leistung zu gewährleisten. Wächst die Warteschlange unkontrolliert, steigt die Latenz und es wird nötig, weitere Verbraucherinstanzen zu starten.

5. Audit‑Trails und Replay

Eine aufbewahrte Ereignishistorie kann helfen, den Zustand zu rekonstruieren, Vorfallanalysen durchzuführen oder neue Projektionen zu bauen. Replay ist sicher, wenn Schemata, Seiteneffekte und Datenschutzregeln sorgfältig und bewusst gehandhabt werden. Denn ein erneutes Abspielen eines Zahlungsevents könnte sonst doppelte Abbuchungen auslösen, weshalb idempotente Verarbeitung unabdingbar ist.

Wann ereignisgesteuerte Architektur passend ist

Gute Einsatzbereiche

  • Mehrere Abonnenten müssen auf dieselbe Tatsache reagieren.
    Nachgelagerte Domänen wie Analytics-Pipelines, Notification-Services und Bestandsbuchungen lassen sich durch einfaches Abonnieren des OrderPlaced-Ereignisstroms ohne Modifikation des Kernsystems anbinden.

  • Arbeiten müssen asynchron erledigt werden, etwa bei Benachrichtigungen oder Indexierung.
    Eine Bestellbestätigung per E‑Mail lässt sich versenden, ohne die Transaktion zur Auftragserzeugung zu blockieren.

  • Bounded Contexts benötigen eine dauerhafte Integrationsgrenze.
    Ein domänenspezifisches Ereignis kann Mikroservice‑Grenzen überschreiten, ohne interne APIs preiszugeben.

  • Warteschlangen können Lastspitzen glätten.
    Ein plötzlicher Ansturm von Bestellungen wird gepuffert, sodass nachgelagerte Dienste nicht überlastet werden.

  • Eine prüfbare Historie ist von Wert.
    Durch das Speichern jedes Zustandswechsels als Ereignis lassen sich regulatorische Audits nachvollziehen.

Schlechte Einsatzbereiche

  • Der Aufrufer verlangt sofort eine stark konsistente Antwort.
    Wenn die Benutzeroberfläche den aktuellen Lagerbestand anzeigen muss, bevor sie fortfährt, ist ein synchroner Aufruf sicherer.

  • Eine Transaktion muss atomar über mehrere Services hinweg gelten.
    Reine Ereignisflüsse können keine ACID‑Transaktion über Service‑Grenzen garantieren; hier sind Sagas oder kompensierende Aktionen nötig.

  • Das System ist so klein, dass ein Broker mehr Aufwand als Nutzen bringt.
    Für eine Anwendung mit lediglich zwei Diensten entsteht durch einen Message‑Broker meist mehr Komplexität als Mehrwert.

  • Ein einfacher synchroner Aufruf ist leichter zu verstehen und zu betreiben.
    Bei rein CRUD‑Operationen kann das Publizieren und Abonnieren die Logik unnötig verwirren.

Ereignisse können in Workflows mit Konsistenzanforderungen eingebunden werden, erfordern dann jedoch zusätzliche Muster wie Sagas, kompensierende Transaktionen oder einen autoritativen Transaktionsspeicher. Diese Muster koordinieren die einzelnen Schritte, sodass das Gesamtsystem trotz asynchroner Kommunikation konsistente Ergebnisse liefert.

Häufige Muster

Ereignisbenachrichtigung

Ein kompaktes Ereignis wird veröffentlicht, das lediglich den Faktum benennt; die Abonnenten holen bei Bedarf weitere Details nach.

eventBus.emit('user.registered', { userId: '123' });

Zustandsübertragung im Ereignis

Das Ereignis transportiert alle erforderlichen Daten, reduziert Rückaufrufe, erhöht jedoch die Payload‑Größe und die Verantwortung für das Schema‑Management.

eventBus.emit('order.placed', {
  orderId: '456',
  customerId: '123',
  items: [...],
  total: 99.99,
  placedAt: new Date(),
});

Ereignisbasierte Zustandsverwaltung

Zustandsänderungen werden als Ereignisse archiviert, und der aktuelle Zustand entsteht durch das Wiedergeben dieser Ereignisse. Das liefert eine lückenlose Historie, verlangt jedoch sorgfältige Versionierung, Datenschutz und die Pflege von Projektionen.

const account = eventStore
  .getEvents('account.123')
  .reduce(applyEvent, initialState);

CQRS

Das Schreibmodell wird vom Lesemodell getrennt, das aus Ereignissen aufgebaut wird. CQRS kann bei großem Maßstab vorteilhaft sein, ist jedoch nicht für jeden ereignisgesteuerten Service zwingend erforderlich. Ein Befehl PlaceOrder erzeugt das Ereignis OrderPlaced, das die Schreibseite aktualisiert; das Lesemodell liefert dann Bestellübersichten aus einer Projektion.

Command: PlaceOrder → OrderPlaced Ereignis → aktualisiert Projektionen
Query: GetOrderSummary → liest aus einer Projektion

Überlegungen zur Umsetzung

Mindestens‑einmal gegenüber genau‑einmaliger Zustellung

Viele Broker garantieren mindestens‑einmalige Zustellung. Deshalb müssen Abonnenten idempotent sein, etwa indem sie die Ereignis‑ oder Befehls‑ID persistieren und die Zustandsänderung nur ausführen, wenn diese ID noch nicht verarbeitet wurde.

async function handleOrderPlaced(event: OrderPlacedEvent) {
  if (await db.wasProcessed(event.id)) return;
  await db.transaction(async (tx) => {
    await tx.createOrder(event);
    await tx.markProcessed(event.id);
  });
}

„Genau‑einmalige Zustellung“ ist meist ein Geschäftseffekt, der durch Idempotenz und Transaktionen erzielt wird, nicht eine Garantie des Transports. In der Praxis behandelt man „genau‑einmal“ als „mindestens‑einmal plus idempotente Verarbeitung“.

Dead‑Letter‑Warteschlangen

Nach wiederholtem Fehlschlag sollten Nachrichten in einer Dead‑Letter‑Warteschlange isoliert werden, damit sie untersucht werden können. Betreiber benötigen Alarme, Replay‑Verfahren und ein Verfahren, um fehlerhafte Daten von temporären Ausfällen zu unterscheiden.

Entwicklung von Ereignisschemata

Ereignisse fungieren als Integrationsverträge. Deshalb sollten additive, rückwärtskompatible Änderungen bevorzugt, bewusst versioniert und Aufbewahrungs‑ sowie Datenschutzregeln dokumentiert werden.

// v1
{ type: 'order.placed', version: 1, orderId: '123' }

// v2
{ type: 'order.placed', version: 2, orderId: '123', channel: 'web' }

Fazit

Ereignisgesteuerte Mechanismen entfalten ihren größten Nutzen dort, wo lose zeitliche Kopplung, asynchrone Skalierung und dauerhaft nachvollziehbare Audit-Ströme gefordert sind. Sie bewähren sich insbesondere bei starkem Fan-out, zur Glättung von Lastspitzen sowie bei Event-Sourcing-Anforderungen. Für einfache Request-Response-Abfragen bleibt synchrone Kommunikation hingegen überlegen, da eventual consistency, Infrastrukturbetrieb und verteilte Fehleranalyse sonst unnötigen Mehraufwand erzeugen.

Quellen & Referenzen

  1. [1]
  2. [2]
  3. [3]
  4. [4]
  5. [5]
  6. [6]
  7. [7]

Comments (0)

Loading comments...
Ereignisgesteuerte Mechanismen: Welche Probleme sie lösen und wann sie passen | Arne's Blog