Read in English · Leer en español · Lire en français
Abschnitt 6.4 von 6.8 · Fortgeschritten · 28 Min. Lesezeit
Ereignisgesteuerte Architektur: Broker, Mediatoren und Event-Streams
Videolektion
5 Min. · auf Englisch gesprochen, mit UntertitelnNur anhören (Audio)
Transkript lesen
In einem ereignisgesteuerten System verkünden Teile, was geschehen ist, und andere Teile reagieren. In dieser Lektion erfahren Sie, wie das funktioniert und wie es zuverlässig bleibt, wenn Nachrichten verloren gehen oder sich wiederholen.
Denken Sie an eine geschäftige Restaurantküche. Ein Kellner heftet einen Bestellzettel an die Leiste, und niemand bekommt gesagt, was zu tun ist. Der Grillkoch sieht ein Steak, die Salatstation einen Salat und der Patissier ein Dessert für später. Jede reagiert in ihrem eigenen Tempo. Macht der Patissier gerade Pause, wartet der Zettel an der Leiste, bis er zurückkommt. Und eine neue Station ändert nichts daran, wie Kellner ihre Zettel schreiben. Ein ereignisgesteuertes System funktioniert genauso.
Drei Wörter werden oft unscharf verwendet. Ein Ereignis ist eine eingetretene Tatsache, etwa „Bestellung aufgegeben“. Beliebig viele Services können es empfangen, und der Publisher weiß nicht, wer sie sind. Ein Command ist eine Anfrage an einen bestimmten Handler, etwa „Zahlung autorisieren“. Anders als ein Ereignis kann es abgelehnt werden. Eine Nachricht ist einfach der Umschlag, der das eine oder das andere transportiert. Eine Queue stellt jede Nachricht einem Consumer zu, was zu Commands passt. Ein Topic gibt jedem Subscriber eine eigene Kopie, was zu Ereignissen passt.
Hier ist die Broker-Topologie, ohne zentralen Koordinator. Der Bestellservice veröffentlicht ein Ereignis „Bestellung aufgegeben“ im orders-Topic und arbeitet weiter. Jeder interessierte Service reagiert in seinem eigenen Tempo: Zahlungen autorisiert die Karte, das Tablet des Restaurants zeigt die Bestellung, Benachrichtigungen sendet eine Bestätigung, und Analytics aktualisiert seine Dashboards. Zahlungen veröffentlicht dann ein eigenes Ereignis, „Zahlung autorisiert“, und die Disposition reagiert, indem sie einen Kurier zuweist. Der Ablauf existiert nur als Summe dieser Reaktionen. Das nennt man Choreografie. Einen Betrugserkennungsservice im nächsten Jahr hinzuzufügen, bedeutet ein neues Abonnement und keinerlei Änderung an den Bestellungen.
In der Mediator-Topologie steuert ein Bestell-Mediator den Ablauf. Er kennt die Schritte, ihre Abfolge und was bei Fehlern zu tun ist. Das nennt man Orchestrierung. Er sendet nacheinander Commands an jeden Service und reagiert auf ihre Antworten. Choreografie koppelt am wenigsten, aber der Gesamtablauf ist schwer zu überblicken. Orchestrierung macht ihn sichtbar, auf die Gefahr eines Engpasses hin. Die meisten realen Systeme mischen beides.
Ein logbasierter Broker wie Apache Kafka bewahrt Ereignisse auf, ob sie gelesen wurden oder nicht. Ein Topic ist in Partitionen aufgeteilt, jede ein geordnetes Log, und jedes Ereignis hat eine Position, seinen Offset. Consumer treten einer Gruppe bei. Innerhalb einer Gruppe wird jede Partition jeweils nur von einem Consumer gelesen, sodass sich die Gruppe die Arbeit teilt. Eine andere Gruppe, etwa Analytics, liest dieselben Ereignisse unabhängig, mit eigenen Offsets. Ereignisse mit demselben Schlüssel, etwa der Nummer einer Bestellung, landen alle geordnet in einer Partition. Aber die Reihenfolge ist nur innerhalb einer Partition garantiert, nie partitionsübergreifend. Und da jede Partition pro Gruppe einen Consumer hat, begrenzt die Zahl der Partitionen die Parallelität. Zwölf Partitionen beschäftigen höchstens zwölf Consumer einer Gruppe.
Wenn nach einem Absturz unklar ist, ob eine Nachricht verarbeitet wurde, muss man Abstriche machen. At-most-once heißt nie doppelt, aber manchmal gar nicht. Das ist in Ordnung für Standortpings von Kurieren, bei denen die nächste Aktualisierung die letzte ersetzt. At-least-once heißt nie verloren, aber manchmal doppelt verarbeitet. Das ist die übliche Wahl für Geschäftsereignisse. Eine Exactly-once-Zustellung über ein unzuverlässiges Netzwerk ist im Allgemeinen nicht erreichbar. Systeme können nur eine Exactly-once-Wirkung innerhalb einer definierten Grenze bieten. Gehen Sie also von at-least-once aus, und machen Sie jeden Consumer idempotent. Geben Sie jedem Ereignis eine eindeutige Kennung, speichern Sie verarbeitete Kennungen in derselben lokalen Transaktion wie die Änderung, und überspringen Sie alle, die Sie schon gesehen haben.
Sobald andere Teams ein Ereignis konsumieren, ist seine Struktur ein Vertrag, und Consumer lesen womöglich Ereignisse, die vor Monaten geschrieben wurden. Geben Sie jedem Ereignis eine Kennung, einen Typ, eine Version und einen Zeitstempel, und ändern Sie es nur kompatibel. Manche Fehler sind vorübergehend, etwa eine kurz nicht verfügbare Datenbank. Wiederholen Sie diese einige Male mit zunehmenden, zufällig gestreuten Verzögerungen. Andere scheitern jedes Mal, etwa ein fehlerhaft aufgebautes Ereignis. Diese Poison Message darf ihre Partition nicht blockieren. Verschieben Sie sie in eine Dead Letter Queue, und sorgen Sie dafür, dass jemand sie beobachtet. Und veröffentlichen Sie Tatsachen, keine getarnten Commands. „Bestätigungs-E-Mail senden“, auf einem Topic veröffentlicht, ist ein Command mit versteckter Kopplung. Veröffentlichen Sie „Bestellung aufgegeben“, und lassen Sie die Benachrichtigungen entscheiden.
Der vollständige Abschnitt behandelt außerdem vier verschiedene Arten, Ereignisse zu nutzen, und Ereignisschemas ausführlicher. Als Nächstes vergleichen wir in Abschnitt sechs Punkt fünf synchrone und asynchrone Kommunikation und das API-Design. Bis gleich.
Was Sie wissen müssen
- Ein Ereignis ist eine eingetretene Tatsache (Vergangenheitsform, viele mögliche Empfänger); ein Command ist eine Anfrage an einen Handler, die abgelehnt werden kann; eine Nachricht ist der Umschlag, der beides transportiert.
- Die Broker-Topologie verteilt einen Ablauf auf unabhängige Reaktionen (Choreografie); die Mediator-Topologie legt die Abfolge in einen Koordinator (Orchestrierung). Choreografie koppelt am wenigsten; Orchestrierung macht Abläufe sichtbar und Ausfälle leichter beherrschbar.
- Fowler unterscheidet Event Notification, Event-Carried State Transfer, Event-Sourcing und CQRS; die ersten beiden sind Wege, wie Services kommunizieren, die letzten beiden sind Wege, wie ein Service Daten speichert.
- In Kafka ist ein Topic in Partitionen aufgeteilt; Ereignisse mit demselben Schlüssel landen in derselben Partition, die Reihenfolge ist nur innerhalb einer Partition garantiert, und jede Consumer Group verteilt die Partitionen auf ihre Mitglieder und verfolgt ihre eigenen Offsets.
- Die Zustellung kann at-most-once, at-least-once oder effectively-once erfolgen; eine Exactly-once-Zustellung ist im Allgemeinen nicht erreichbar, daher sollten Sie von at-least-once ausgehen und jeden Consumer idempotent machen.
- Veröffentlichte Ereignisse sind langlebige Verträge: Geben Sie ihnen Kennungen, Versionen und Zeitstempel, entwickeln Sie sie kompatibel weiter und prüfen Sie sie mit einer Schema Registry.
- Wiederholen Sie vorübergehende Fehler mit Backoff, verschieben Sie Poison Messages in eine überwachte Dead Letter Queue, und lassen Sie nie zu, dass eine einzige fehlerhafte Nachricht eine Partition blockiert.
Definition: auf Geschehnisse reagieren
In einer ereignisgesteuerten Architektur (EDA) kommunizieren Komponenten, indem sie Ereignisse erzeugen und auf sie reagieren: Aufzeichnungen von Tatsachen, die im Geschäft eingetreten sind, etwa eine Bestellung wurde aufgegeben oder ein Kurier wurde zugewiesen. Die Komponente, die ein Ereignis veröffentlicht, ruft die Komponenten, die darauf reagieren, nicht auf und weiß meist nicht einmal, dass es sie gibt. Ein Message Broker transportiert Ereignisse von Produzenten zu Consumern, hält sie vor, während Consumer beschäftigt oder ausgefallen sind, und erlaubt es, neue Consumer hinzuzufügen, ohne den Produzenten zu ändern.
Ereignisgesteuerte Designs sind nicht neu: Grafische Benutzeroberflächen, Betriebssysteme und Handelssysteme arbeiten seit Jahrzehnten so, und Enterprise-Messaging-Produkte sind älter als das Web. Was sich in den 2010er-Jahren änderte, waren Größenordnung und Dauerhaftigkeit. Logbasierte Broker wie Apache Kafka, entstanden bei LinkedIn und 2011 als Open Source veröffentlicht, machten es praktikabel, jedes Geschäftsereignis tage- oder jahrelang aufzubewahren und erneut abzuspielen. EDA kann der Hauptstil eines Systems sein oder, häufiger, die Art, wie die Services eines Microservice- oder servicebasierten Systems miteinander sprechen.
Ereignisse, Commands und Nachrichten
Drei Wörter werden oft unscharf verwendet, und der Unterschied ist wichtig. Eine Nachricht ist der Umschlag: beliebige Daten, die von einem Programm über einen Kanal an ein anderes gesendet werden. Was die Nachricht transportiert, kann ein Ereignis sein (eine Tatsache, in der Vergangenheitsform benannt), ein Command (eine Anfrage an einen Handler, als Anweisung benannt) oder eine Abfrage und ihre Antwort. Gregor Hohpe und Bobby Woolf haben diese Nachrichtentypen und die Kanäle, die sie transportieren, in Enterprise Integration Patterns (2003) katalogisiert, und ihr Vokabular ist bis heute der Standard.
Ereignis, Command und Nachricht
| Ereignis (Event) | Command | Nachricht (der Umschlag) | |
|---|---|---|---|
| Bedeutung | Eine Tatsache: etwas, das geschehen ist | Eine Anfrage: Bitte tu etwas | Beliebige Daten, die von einem Programm über einen Kanal an ein anderes gesendet werden |
| Benennung | Vergangenheitsform: OrderPlaced, PaymentAuthorised | Imperativ: AuthorisePayment, AssignCourier | Transportiert ein Ereignis, ein Command, eine Abfrage oder eine Antwort |
| Empfänger | Null, einer oder viele; der Publisher kennt sie nicht | Genau ein vorgesehener Handler | Hängt vom Kanal ab: eine Queue (einer) oder ein Topic (viele) |
| Kann es abgelehnt werden? | Nein; es ist bereits geschehen. Die Empfänger entscheiden, wie sie reagieren | Ja; der Handler kann es ablehnen oder daran scheitern | Nicht anwendbar |
| Kopplung | Der Publisher hängt nicht von den Consumern ab | Der Sender hängt von der Fähigkeit und dem Vertrag des Empfängers ab | Hängt davon ab, was sie transportiert |
Nachrichten laufen über zwei Arten von Kanälen. Eine Queue stellt jede Nachricht einem ihrer Consumer zu, was zu Commands und zur Arbeitsverteilung passt. Ein Topic stellt jedem Subscriber eine Kopie jeder Nachricht zu; das ist Publish-Subscribe (Pub-Sub) und passt zu Ereignissen. Ein Design, das OrderPlaced an ein Topic sendet, lässt Zahlungen, Restaurant, Benachrichtigungen und Analytics reagieren, jeden in seinem eigenen Tempo; einen Betrugserkennungsservice im nächsten Jahr hinzuzufügen, bedeutet ein neues Abonnement und keine Änderung an den Bestellungen.
Broker-Topologie und Mediator-Topologie
Richards und Ford beschreiben zwei Topologien für ereignisgesteuerte Systeme. In der Broker-Topologie gibt es keinen zentralen Koordinator. Ein auslösendes Ereignis wird veröffentlicht; jeder interessierte Prozessor reagiert und kann weitere Ereignisse veröffentlichen, auf die wiederum andere reagieren. Der Ablauf existiert nur als Summe dieser Reaktionen. Das ist Choreografie: Wie Tänzer, von denen jeder seine eigenen Schritte kennt, lenkt niemand das Ganze.
In der Mediator-Topologie empfängt ein Event-Mediator das auslösende Ereignis und steuert den Ablauf, indem er Commands in einer festgelegten Reihenfolge an Prozessoren sendet und auf ihre Antworten reagiert. Das ist Orchestrierung: Ein Dirigent kennt die Partitur. Mediatoren reichen von einem einfachen Service mit Zustandsautomat bis zu einer Workflow-Engine wie Temporal, AWS Step Functions oder einer BPMN-Engine.
Broker-Topologie: der OrderPlaced-Ablauf
Mediator-Topologie: derselbe Ablauf, orchestriert
Choreografie oder Orchestrierung?
| Frage | Choreografie (Broker) | Orchestrierung (Mediator) |
|---|---|---|
| Kopplung | Am geringsten: Publisher kennen die Consumer nicht | Der Mediator hängt vom Vertrag jedes Beteiligten ab |
| Den gesamten Ablauf sehen | Schwer: Er ist über Services verteilt; zur Rekonstruktion braucht es Tracing | Leicht: Er ist an einer Stelle niedergeschrieben |
| Fehlerbehandlung | Jeder Beteiligte muss Fehler erkennen und darauf reagieren, oft durch Veröffentlichen kompensierender Ereignisse | Der Mediator entscheidet zentral über Wiederholungen, Timeouts und Kompensationen |
| Einen Schritt hinzufügen | Einen Subscriber hinzufügen; sonst ändert sich nichts | Den Mediator ändern |
| Risiken | Zyklische Ereignisketten; niemand bemerkt, wenn ein Schritt stillschweigend ausfällt | Der Mediator wird zum Engpass oder sammelt Geschäftslogik an |
| Gut geeignet für | Einfache, stabile Abläufe; viele unabhängige Reaktionen auf eine Tatsache | Lange, bedingte Abläufe mit Kompensation, etwa die Bestell-Saga |
Die meisten realen Systeme mischen beides: Choreografie zwischen Bounded Contexts (die Bestellungen verkünden Tatsachen und kümmern sich nicht darum, wer zuhört) und Orchestrierung innerhalb eines Kontexts, wo ein mehrstufiger Prozess einen klaren Verantwortlichen braucht.
Vier Arten, Ereignisse zu nutzen
In einem Artikel von 2017 wies Martin Fowler darauf hin, dass „ereignisgesteuert“ für mindestens vier ganz verschiedene Muster verwendet wird und dass Gespräche schiefgehen, wenn Menschen verschiedene davon meinen. Sie auseinanderzuhalten, ist eine der nützlichsten Gewohnheiten, die ein Architekt haben kann.
Fowlers vier Event-Muster (2017)
| Muster | Was das Ereignis transportiert und warum | Nutzen | Kosten |
|---|---|---|---|
| Event Notification | Ein kleines Signal, dass sich etwas geändert hat, oft nur eine Kennung: OrderPlaced {order_id}. Empfänger fragen bei Bedarf die Details nach. | Sehr geringe Kopplung; das Modell des Publishers bleibt privat. | Rückfragen erzeugen Last und eine Laufzeitabhängigkeit vom Publisher; der Ablauf über viele Ereignisse hinweg ist schwer zu überblicken. |
| Event-Carried State Transfer | Genug Daten, damit Empfänger eine eigene Kopie führen können: die vollständige Bestellung mit Positionen und Summen. | Empfänger arbeiten, ohne den Publisher aufzurufen, selbst wenn er ausgefallen ist; geringere Latenz. | Daten werden weit verteilt kopiert und sind eventual consistent; größere Ereignisse; Schemas werden zu Verträgen. |
| Event-Sourcing | Jede Änderung am Zustand einer Entität wird als Ereignis gespeichert; das Ereignisprotokoll ist die maßgebliche Quelle, und der aktuelle Zustand wird durch erneutes Abspielen abgeleitet. | Vollständiger Audit-Trail; frühere Zustände rekonstruieren; neue Sichten aus alter Historie. | Komplex; erneutes Abspielen ist schwierig, wenn Ergebnisse von externen Systemen abhingen; Ereignisschemas müssen sich für immer weiterentwickeln. |
| CQRS | Getrennte Modelle (oft getrennte Speicher) für das Schreiben und das Lesen, häufig über Ereignisse gespeist. | Jede Seite ist für ihre Aufgabe optimiert; passt zu leseintensiven Systemen mit komplexen Abfragen. | Zwei Modelle, die synchron gehalten werden müssen; oft dort eingesetzt, wo ein Modell genügen würde. |
Event Notification und Event-Carried State Transfer betreffen die Kommunikation zwischen Services und gehören in dieses Kapitel. Event-Sourcing und CQRS betreffen die Art, wie ein Service seine Daten speichert; sie werden oft mit EDA kombiniert, sind aber unabhängige Entscheidungen und werden in Kapitel 7 behandelt.
Event-Streams und Logs: wie Kafka funktioniert
Herkömmliche Message Queues löschen eine Nachricht, sobald ein Consumer sie bestätigt. Ein logbasierter Broker wie Apache Kafka speichert Ereignisse stattdessen in einem nur anfügbaren Log und bewahrt sie für eine konfigurierte Aufbewahrungsdauer (oder für immer) auf, unabhängig davon, ob sie gelesen wurden. Aus diesem Design folgen mehrere Dinge, und sie erklären sowohl Kafkas Stärken als auch seine Fallstricke.
Ein Topic ist in Partitionen aufgeteilt, von denen jede ein geordnetes Log ist, das über Broker hinweg gespeichert und repliziert wird. Ein Produzent wählt die Partition, normalerweise durch Hashing des Ereignisschlüssels, sodass alle Ereignisse mit demselben Schlüssel (etwa derselben Bestell-ID) in derselben Partition landen. Jedes Ereignis in einer Partition hat eine fortlaufende Position, seinen Offset. Consumer holen Ereignisse ab und speichern den Offset, den sie erreicht haben; sie können auch zurückspulen und erneut abspielen.
Consumer treten einer Consumer Group bei. Innerhalb einer Gruppe wird jede Partition zu jedem Zeitpunkt von genau einem Consumer gelesen, sodass sich die Gruppe die Arbeit teilt; verschiedene Gruppen lesen unabhängig voneinander, jede mit ihren eigenen Offsets, und so wird Pub-Sub erreicht. Die Zahl der Partitionen begrenzt daher die Parallelität einer Gruppe: Ein Topic mit 12 Partitionen kann höchstens 12 Consumer einer Gruppe beschäftigen. (Kafka 4.2, 2026 veröffentlicht, hat Share Groups als Queue-ähnliche Alternative hinzugefügt: Mehrere Consumer können sich eine Partition teilen und Nachrichten einzeln bestätigen, die Reihenfolge innerhalb der Partition bleibt dann aber nicht erhalten. Dieser Abschnitt beschreibt klassische Consumer Groups.)
Kafka garantiert die Reihenfolge nur innerhalb einer Partition. Ereignisse zu einer Bestellung kommen in der Reihenfolge an, in der sie geschrieben wurden, wenn sie einen Schlüssel teilen; Ereignisse zu verschiedenen Bestellungen in verschiedenen Partitionen haben zueinander keine definierte Reihenfolge. Designs, die eine globale Reihenfolge brauchen, müssen eine einzige Partition verwenden (und damit auf Parallelität verzichten) oder, besser, so umgestaltet werden, dass nur die Reihenfolge pro Entität zählt.
Partitionen, Offsets und Consumer Groups
Zustellgarantien und was „exactly once“ wirklich bedeutet
Weil Netzwerke und Prozesse ausfallen (Abschnitt 6.1), müssen ein Broker und seine Clients festlegen, was geschieht, wenn nach einem Absturz oder Timeout unklar ist, ob eine Nachricht verarbeitet wurde. Es gibt drei mögliche Garantien, und welche gilt, hängt davon ab, wann Bestätigungen und Offsets gespeichert werden.
Zustellgarantien
| Garantie | Was sie verspricht | Wie sie entsteht | Typische Folge |
|---|---|---|---|
| At-most-once | Jede Nachricht wird einmal oder gar nicht verarbeitet, nie zweimal. | Der Consumer speichert seine Position (committet den Offset oder bestätigt) vor der Verarbeitung, oder der Produzent wiederholt nie. | Nachrichten können nach einem Absturz verloren gehen. Akzeptabel für Messwerte oder Standortpings von Kurieren, bei denen die nächste Aktualisierung die letzte ersetzt. |
| At-least-once | Jede Nachricht wird einmal oder mehrmals verarbeitet, geht aber nie verloren. | Der Produzent wiederholt, bis er eine Bestätigung erhält, und der Consumer committet seine Position erst nach der Verarbeitung. | Duplikate nach Abstürzen, Wiederholungen und Rebalances. Der Standard der meisten Broker und die übliche Wahl für Geschäftsereignisse. |
| Effectively-once | Die Wirkung jeder Nachricht wird genau einmal angewendet, auch wenn sie mehr als einmal zugestellt werden kann. | At-least-once-Zustellung plus ein idempotenter Consumer oder eine Transaktion, die das Ergebnis und die Position des Consumers gemeinsam speichert. | Die praktische Bedeutung von „exactly once“ in Geschäftssystemen; erfordert bewusstes Design in jedem Consumer. |
„Exactly once“ muss sorgfältig definiert werden. Eine Exactly-once-Zustellung über ein unzuverlässiges Netzwerk ist im Allgemeinen nicht erreichbar: Ein Sender, der keine Bestätigung erhält, kann nicht wissen, ob er erneut senden soll. Was Systeme bieten können, ist eine Exactly-once-Wirkung innerhalb einer definierten Grenze. Kafka zum Beispiel bietet seit Version 0.11 (2017) einen idempotenten Produzenten und Transaktionen, sodass ein Lesen-Verarbeiten-Schreiben-Zyklus von Kafka-Topics zu Kafka-Topics seine Ausgabe und seine Consumer-Offsets atomar committet. Diese Garantie endet an der Grenze von Kafka. Belastet der Consumer eine Karte, sendet er eine E-Mail oder schreibt er in eine andere Datenbank, führt ein Absturz zwischen dieser Aktion und dem Offset-Commit dazu, dass die Aktion wiederholt wird, es sei denn, der Consumer selbst ist idempotent oder speichert seine Offsets in derselben Transaktion wie seine Ergebnisse.
Die praktische Regel für Architekten ist daher einfach: Gehen Sie von At-least-once-Zustellung aus und machen Sie jeden Consumer zu einem idempotenten Consumer. Geben Sie jedem Ereignis eine eindeutige Kennung; speichern Sie im Consumer die verarbeiteten Kennungen in derselben lokalen Transaktion wie die Zustandsänderung, und überspringen Sie jedes bereits gespeicherte Ereignis. Machen Sie die Operation nach Möglichkeit auch von Natur aus idempotent, etwa „Status auf PAID setzen, wenn er AWAITING_PAYMENT ist“ statt „eine Zahlung hinzufügen“.
# Idempotent consumer: processing the same event twice has the effect of once
def handle_payment_authorised(event, db):
with db.transaction(): # one local ACID transaction
inserted = db.execute(
"INSERT INTO processed_events (event_id) VALUES (%s) "
"ON CONFLICT (event_id) DO NOTHING",
[event["event_id"]],
).rowcount
if inserted == 0:
return # seen before: do nothing
db.execute(
"UPDATE orders SET status = 'PAID' WHERE order_id = %s "
"AND status = 'AWAITING_PAYMENT'",
[event["order_id"]],
)
# commit the broker offset only after the transaction has committed
Ereignisschemas und Versionierung
Sobald andere Teams ein Ereignis konsumieren, ist seine Struktur ein veröffentlichter Vertrag, genau wie eine API, und er hält länger: Bei einem logbasierten Broker können Consumer Ereignisse lesen, die vor Monaten geschrieben wurden. Ein gutes Ereignis hat eine eindeutige Kennung, einen Typ, eine Schemaversion, den Zeitpunkt seines Eintretens, die Kennungen der beteiligten Entitäten und die Geschäftsdaten, die Consumer brauchen. Geldbeträge werden in der kleinsten Währungseinheit mit Angabe der Währung gespeichert, nie als Gleitkommazahl (Kapitel 0).
{
"event_id": "9f1c2e7a-4b0d-4e55-9a51-0c3d8f2b6a10",
"type": "OrderPlaced",
"schema_version": 2,
"occurred_at": "2026-10-22T18:42:07Z",
"order_id": "ord-4711",
"customer_id": "cus-208",
"restaurant_id": "res-31",
"items": [{"dish_id": "d-5", "qty": 2, "price_minor": 1150}],
"total_minor": 2450,
"currency": "GBP"
}Schemas sollten sich abwärtskompatibel weiterentwickeln: optionale Felder hinzufügen, Felder nie ohne neue Version entfernen, umbenennen oder in ihrer Bedeutung ändern und einen Feldnamen nie für etwas anderes wiederverwenden. Viele Teams setzen das mit einem Schemaformat wie Avro, Protocol Buffers oder JSON Schema und einer Schema Registry durch, die inkompatible Änderungen beim Veröffentlichen zurückweist. Die CloudEvents-Spezifikation, ein graduiertes Projekt der Cloud Native Computing Foundation, standardisiert die gemeinsamen Umschlagattribute wie id, source, type und time. Kompatibilitätsmodi und Migrationstechniken werden unter Schemaevolution behandelt.
Fehlerbehandlung: Wiederholungen, Poison Messages und Dead Letter Queues
Consumer scheitern aus zwei sehr verschiedenen Gründen, und diese brauchen eine unterschiedliche Behandlung. Vorübergehende Fehler, etwa eine kurzzeitig nicht verfügbare Datenbank oder ein Timeout beim Aufruf eines anderen Service, gelingen meist beim erneuten Versuch; wiederholen Sie einige Male mit exponentiellem Backoff und Jitter (zunehmenden, zufällig gestreuten Verzögerungen), damit eine angeschlagene Abhängigkeit nicht überlastet wird. Dauerhafte Fehler, etwa ein fehlerhaft aufgebautes Ereignis oder eines, das gegen eine Geschäftsregel verstößt, scheitern jedes Mal. Eine solche „Poison Message“ darf den Consumer nicht für immer blockieren, was auf einem partitionierten Log jedes spätere Ereignis in dieser Partition aufhalten würde.
Die Standardlösung ist eine Dead Letter Queue (DLQ): Nach einer festgelegten Zahl von Versuchen wird die Nachricht in einen separaten Kanal verschoben, der Fehler wird festgehalten, und der Consumer macht weiter. Eine DLQ ist nur nützlich, wenn jemand sie beobachtet: Alarmieren Sie bei ihrer Größe, geben Sie einem benannten Team die Verantwortung, stellen Sie Werkzeuge bereit, um Nachrichten zu untersuchen, zu korrigieren und erneut abzuspielen, und legen Sie vorab fest, welche Reihenfolgegarantien verloren gehen, wenn eine Nachricht verspätet erneut abgespielt wird. Manche Teams schalten vor der DLQ ein oder mehrere Retry-Topics mit längeren Verzögerungen dazwischen.
Merkmale der ereignisgesteuerten Architektur
| Merkmal | Bewertung | Grund |
|---|---|---|
| Skalierbarkeit und Elastizität | Hoch | Consumer skalieren unabhängig und fangen Lastspitzen aus dem Puffer des Brokers ab; Partitionen ermöglichen Parallelität. |
| Reaktionsfähigkeit | Hoch | Der Publisher kehrt zurück, sobald das Ereignis dauerhaft gespeichert ist; langsame Arbeit geschieht später. |
| Fehlertoleranz | Hoch | Ein Consumer kann minutenlang ausfallen und vom Broker aus aufholen; Ausfälle pflanzen sich nicht zum Publisher zurück fort. |
| Erweiterbarkeit | Hoch | Neue Consumer abonnieren bestehende Ereignisse, ohne dass sich am Publisher etwas ändert. |
| Datenkonsistenz | Niedrig | Zustand verbreitet sich mit Verzögerung über Services; Leser sehen eine Zeit lang veraltete Daten. |
| Testbarkeit und Debugging | Niedrig | Das Verhalten entsteht aus asynchronen Reaktionen; eine Abfolge von Ereignissen über Services hinweg nachzustellen, ist ohne gutes Tracing schwierig. |
| Sichtbarkeit des Ablaufs | Niedrig (Choreografie) bis mittel (Mediator) | Bei Choreografie verantwortet niemand den End-to-End-Ablauf; ein Mediator macht ihn explizit. |
| Einfachheit | Niedrig | Broker, Schemas, Wiederholungen, Dead-Letter-Behandlung, Reihenfolge und Duplikate müssen alle entworfen werden. |
Wann man sie einsetzt, und häufige Fehler
Ereignisgesteuerte Architektur passt, wenn viele Teile eines Geschäfts auf dieselben Tatsachen reagieren müssen, wenn Arbeit erledigt werden kann, nachdem die Anfrage des Benutzers beantwortet ist, wenn die Last schubweise kommt und geglättet werden muss und wenn mit der Zeit neue Consumer zu erwarten sind. Weniger gut passt sie, wenn ein Benutzer eine sofortige, endgültige Antwort braucht, die von mehreren Services abhängt (obwohl ein synchroner Aufruf für die Antwort plus Ereignisse für die Folgeschritte ein gängiger Hybrid ist), oder wenn starke Konsistenz über Services hinweg erforderlich ist.
Prüfen Sie Ihr Verständnis
Ist ReserveCourier ein Ereignis oder ein Command, und woran würden Sie das erkennen?
Ein Command: Es ist eine Anweisung an einen Handler (die Disposition), der sie ablehnen kann. Ein Ereignis wäre in der Vergangenheitsform benannt, etwa CourierReserved.
Ein Kafka-Topic hat 6 Partitionen, und eine Consumer Group hat 10 Consumer. Wie viele Consumer sind beschäftigt?
Höchstens 6. Innerhalb einer Gruppe wird jede Partition einem Consumer zugewiesen, sodass 4 Consumer untätig bleiben.
Warum verhindert Kafkas Exactly-once-Unterstützung nicht, dass ein Consumer eine Bestätigungs-E-Mail zweimal sendet?
Seine Transaktionen umfassen das Lesen aus und Schreiben in Kafka-Topics sowie das Committen von Offsets; eine an ein externes System gesendete E-Mail liegt außerhalb dieser Grenze, sodass ein Absturz vor dem Offset-Commit zu erneuter Verarbeitung führt, es sei denn, der Consumer ist idempotent.
Was ist der Unterschied zwischen Event Notification und Event-Carried State Transfer?
Notification sendet ein kleines Signal, oft nur eine Kennung, sodass Empfänger die Details nachfragen; State Transfer enthält die Daten, die Empfänger brauchen, sodass sie eine eigene Kopie führen und Aufrufe beim Publisher vermeiden können.
Quellen und weiterführende Literatur
Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.
Fragen: „Unsere Bestellereignisse laufen über Kafka, und manche Kunden erhalten gelegentlich zwei Bestätigungs-E-Mails. Was passiert da, und wie sollten wir den Consumer gestalten?“