Enterprise Integration Patterns

Nachrichtenaufbau

Enterprise Integration Patterns · Gruppe 4 von 8

Lesestand

Diese Gruppe beschäftigt sich mit der Nachricht selbst: Wozu dient sie, und welche Angaben braucht sie im Header, damit das Zusammenspiel funktioniert? Die ersten drei Muster beschreiben den Zweck einer Nachricht: Auftrag, Daten oder Ereignis. Die nächsten drei regeln, wie aus Einbahnstraßen Anfrage und Antwort werden. Die letzten drei sind Header-Angaben für Sonderfälle: große Daten, verfallende Daten und sich ändernde Formate.

  • Anwendung
  • Integrationskomponente
  • Kanal
  • Nachricht
  • Datenbank / Speicher

20. Command Message · Befehlsnachricht

ShopLager{ "befehl": "versende", "bestellung": 4711 }Imperativ: „Tu das!“führt aus
Der Shop beauftragt das Lager per Nachricht, eine Bestellung zu versenden.

Die Idee

Eine Command Message ist ein Auftrag: Sie fordert den Empfänger auf, etwas Bestimmtes zu tun. Sie ist das Messaging-Gegenstück zu einem Methodenaufruf, nur dass der Aufrufer nicht wartet. Ihr Name ist meist ein Verb im Imperativ: VersendeBestellung, ErstelleRechnung, SperreKonto. Das entspricht dem Command-Muster der GoF: Ein Aufruf wird zu einem Objekt, das man verschicken und speichern kann.

Beispiel

Nachdem die Zahlung eingegangen ist, schickt der Shop die Nachricht VersendeBestellung(4711) an das Lager. Das Lager führt den Auftrag aus, sobald es dazu kommt. Ob es dafür eine Minute oder eine Stunde braucht, muss den Shop nicht kümmern.

Worauf achten

Ein Befehl hat einen bestimmten Empfänger, der zuständig ist, und soll genau einmal ausgeführt werden. Er gehört deshalb in einen Point-to-Point Channel. Weil Nachrichten im Fehlerfall doppelt ankommen können, sollte der Befehl so gestaltet sein, dass eine Wiederholung keinen Schaden anrichtet: Eine Bestellung, die schon versendet wurde, wird nicht ein zweites Mal verschickt.

In der Praxis

Aufträge an Hintergrunddienste wie „Erzeuge PDF“ oder „Sende E-Mail“, Job-Queues, die Command-Seite in CQRS-Architekturen und die Schritte einer Saga in Microservice-Abläufen.

21. Document Message · Dokumentnachricht

CRMBuchhaltung{ "kunde": 815, "name": "Sven Berg","adresse": "Hafenstr. 7, Hamburg" }Daten, keine Anweisungentscheidet selbst,was es damit tut
Das CRM schickt der Buchhaltung die vollständigen Kundendaten. Was die Buchhaltung damit macht, ist ihre Sache.

Die Idee

Eine Document Message transportiert Daten, keine Anweisung. Der Sender liefert einen vollständigen Datensatz, etwa einen Kunden, eine Rechnung oder einen Katalog, und der Empfänger entscheidet selbst, was er damit anfängt. Der Inhalt ist das Wichtige, nicht der Zeitpunkt.

Beispiel

Das CRM-System verschickt die kompletten Stammdaten eines Kunden, nachdem sie geändert wurden. Die Buchhaltung speichert die neue Rechnungsadresse, das Versandsystem die neue Lieferadresse, und das Reporting zählt nur mit. Keines der Systeme muss beim CRM nachfragen, alle nötigen Daten stehen in der Nachricht.

Worauf achten

Document Messages sind oft größer als die anderen Arten, weil sie den vollständigen Zustand enthalten. Das macht den Empfänger unabhängig, bläht aber die Nachrichten auf. Bei sehr großen Dokumenten verschickt man besser nur einen Verweis (Claim Check, Gruppe 6). Die Grenze zu den anderen Arten ist fließend: Eine Antwort auf eine Anfrage ist meist eine Document Message, und viele Event Messages enthalten zusätzlich die geänderten Daten.

In der Praxis

Stammdatenabgleich zwischen Systemen, EDI-Nachrichten im Handel (Rechnungen, Lieferscheine), Antworten auf Abfragen und sogenannte Event-Carried State Transfer-Ereignisse, die den neuen Zustand gleich mitliefern.

22. Event Message · Ereignisnachricht

Shop"BestellungEingegangen"LagerBuchhaltungNewsletterVergangenheit:„Das ist passiert.“
Der Shop meldet nur, dass eine Bestellung eingegangen ist. Wer darauf reagiert, bestimmen die Empfänger.

Die Idee

Eine Event Message meldet ein Ereignis: Etwas ist passiert. Der Sender gibt keine Anweisung und erwartet keine Antwort. Er teilt nur mit, was geschehen ist, und überlässt es den Empfängern, ob und wie sie reagieren. Ihr Name steht deshalb in der Vergangenheit: BestellungEingegangen, ZahlungErhalten, KundeUmgezogen.

Beispiel

Der Shop meldet BestellungEingegangen. Das Lager reserviert die Ware, die Buchhaltung bereitet eine Rechnung vor, und der Newsletter-Dienst merkt sich das Kaufinteresse. Der Shop weiß nicht, wer zuhört. Kommt ein neuer Dienst hinzu, etwa ein Betrugserkennungssystem, abonniert er das Ereignis, und der Shop bleibt unverändert.

Worauf achten

Der Unterschied zwischen Command und Event ist grundlegend: Ein Command sagt „Tu das!“ und hat genau einen Empfänger, der zuständig ist. Ein Event sagt „Das ist passiert“ und hat beliebig viele oder auch gar keine Empfänger. Events gehören deshalb in einen Publish-Subscribe Channel. Bei Events gibt es zwei Stile: dünne Events mit nur einer ID (Empfänger müssen Details nachfragen) und dicke Events, die alle relevanten Daten mitliefern.

In der Praxis

Die Grundlage ereignisgetriebener Architekturen, Domain Events aus dem Domain-Driven Design, Event Sourcing, Kafka-Topics mit Geschäftsereignissen und Webhooks, die externe Dienste über Ereignisse informieren.

23. Request-Reply · Anfrage und Antwort

ShopLagerAnfrage-Kanal: "bestand-anfragen"Antwort-Kanal: "bestand-antworten"Anfrage: bestand("4711")Antwort: 12 Stück
Die Anfrage läuft über einen Kanal hin, die Antwort über einen zweiten zurück.

Die Idee

Messaging ist von Natur aus eine Einbahnstraße: Eine Nachricht geht vom Sender zum Empfänger, und damit ist der Vorgang beendet. Braucht der Sender eine Antwort, verwendet man zwei Kanäle: einen für die Anfrage und einen für die Antwort. Der Empfänger der Anfrage wird dabei zum Absender der Antwort.

Beispiel

Der Shop schickt eine Anfrage bestand("4711") in den Kanal bestand-anfragen. Das Lager prüft den Bestand und schickt „12 Stück“ in den Kanal bestand-antworten. Der Shop liest die Antwort von dort.

Worauf achten

Der Anfragende kann auf zwei Arten auf die Antwort warten: blockierend, indem er nach dem Senden im Antwortkanal wartet (einfach, aber dann ähnelt das Ganze stark einem direkten Aufruf), oder asynchron, indem er weiterarbeitet und die Antwort verarbeitet, sobald sie kommt. Zwei Fragen stellen sich immer: Woher weiß der Empfänger, wohin er antworten soll (Return Address, Muster 24)? Und welche Antwort gehört zu welcher Anfrage (Correlation Identifier, Muster 25)? Außerdem braucht jede Anfrage ein Zeitlimit für den Fall, dass nie eine Antwort kommt.

In der Praxis

Das Header-Feld JMSReplyTo mit temporären Antwort-Queues in JMS, das reply_to-Feld in RabbitMQ (das Tutorial nennt es „RPC“), ReplyingKafkaTemplate in Spring Kafka und inOut-Routen in Apache Camel.

24. Return Address · Rücksendeadresse

ShopKasseLagerantwort-an: shopantwort-an: kasseAntwort an "shop-antworten"Antwort an "kasse-antworten"
Shop und Kasse nutzen denselben Anfragekanal. Jede Anfrage trägt im Header ihre eigene Antwortadresse.

Die Idee

Ein Dienst, der Anfragen beantwortet, kann viele verschiedene Auftraggeber haben. Damit er nicht fest einprogrammieren muss, wohin jede Antwort geht, schreibt der Anfragende den Antwortkanal in den Header der Anfrage, wie einen Absender auf einem Briefumschlag. Der Antwortende liest das Feld und schickt die Antwort genau dorthin.

Beispiel

Sowohl der Online-Shop als auch die Kassen in den Filialen fragen beim Lager den Bestand ab, beide über denselben Kanal bestand-anfragen. Der Shop setzt im Header antwort-an: shop-antworten, die Kasse antwort-an: kasse-antworten. Das Lager muss die beiden nicht kennen. Kommt eine App hinzu, gibt sie einfach ihren eigenen Antwortkanal an.

Worauf achten

Die Return Address macht den antwortenden Dienst wiederverwendbar, weil er nichts über seine Auftraggeber wissen muss. Häufig wird für jede einzelne Anfrage oder jede Instanz des Anfragenden ein eigener, temporärer Antwortkanal angelegt, der nach Erhalt der Antwort wieder verschwindet. Fehlt die Return Address, sollte der Dienst das als Fehler behandeln, statt die Antwort in einen Standardkanal zu werfen.

In der Praxis

JMSReplyTo in JMS, die Eigenschaft reply_to in RabbitMQ (oft mit der eingebauten Direct-Reply-To-Pseudo-Queue), der Header KafkaHeaders.REPLY_TOPIC in Spring Kafka. Bei E-Mail erfüllt der Header Reply-To genau denselben Zweck.

25. Correlation Identifier · Korrelationskennung

ShopLagerid: 3id: 2id: 1zu: 1zu: 3zu: 2Anfragen in der Reihenfolge 1, 2, 3Antworten in anderer Reihenfolge, aber eindeutig zuzuordnen
Die Antworten kommen in anderer Reihenfolge zurück. Über die Kennung weiß der Shop trotzdem, welche Antwort zu welcher Anfrage gehört.

Die Idee

Stellt ein System viele Anfragen hintereinander, kommen die Antworten nicht unbedingt in derselben Reihenfolge zurück. Damit der Anfragende jede Antwort richtig zuordnen kann, bekommt jede Anfrage eine eindeutige Kennung. Der Antwortende kopiert sie in seine Antwort, in ein eigenes Header-Feld, die Correlation ID. Wie ein Aktenzeichen, das man bei jedem Schreiben an eine Behörde angibt.

Beispiel

Der Shop fragt den Bestand für drei Artikel ab. Die Anfragen haben die IDs 1, 2 und 3. Das Lager beantwortet die einfache Anfrage 1 sofort, für Anfrage 3 schaut es in einem zweiten Lager nach, und Anfrage 2 dauert am längsten. Die Antworten kommen in der Reihenfolge 1, 3, 2. Über das Feld „zu“ weiß der Shop dennoch genau, welcher Bestand zu welchem Artikel gehört.

Worauf achten

Die Kennung muss eindeutig sein, typischerweise eine UUID. Meist verwendet man einfach die Nachrichten-ID der Anfrage als Correlation ID der Antwort. Der Anfragende muss sich offene Anfragen merken, etwa in einer Map von ID auf wartende Aufgabe, und Einträge entfernen, deren Antwort zu lange ausbleibt. Correlation IDs sind auch über Request-Reply hinaus nützlich: Wandert eine ID durch alle Nachrichten eines Geschäftsvorgangs, lässt sich der Vorgang später in den Logs aller Systeme verfolgen.

In der Praxis

JMSCorrelationID in JMS, correlation_id in RabbitMQ, KafkaHeaders.CORRELATION_ID in Spring Kafka. Tracing-Systeme wie OpenTelemetry tragen nach demselben Prinzip eine Trace-ID durch alle beteiligten Dienste.

26. Message Sequence · Nachrichtenfolge

Katalog, 30 MB3/3 Ende2/31/3wieder vollständigjede Teilnachricht trägt Sequenz-ID, Nummer und Ende-Kennungsequenz: K-2026-10
Der Katalog wird in drei Teile zerlegt. Der Empfänger setzt sie anhand der Nummern wieder zusammen.

Die Idee

Manche Daten sind zu groß für eine einzelne Nachricht, weil Broker oft eine Größengrenze haben. Dann zerlegt der Sender sie in mehrere Teilnachrichten. Jede trägt drei Angaben im Header: eine gemeinsame Sequenz-ID, die alle Teile verbindet, ihre Position in der Folge und eine Kennung, welcher Teil der letzte ist. Der Empfänger sammelt die Teile und setzt sie wieder zusammen.

Beispiel

Der Großhändler schickt jeden Monat seinen vollständigen Produktkatalog von 30 MB an den Shop. Der Broker erlaubt höchstens 10 MB pro Nachricht. Der Katalog wird deshalb in drei Nachrichten mit der Sequenz-ID K-2026-10 und den Positionen 1, 2 und 3 verschickt, wobei Teil 3 als letzter markiert ist. Der Shop wartet, bis alle drei da sind, und importiert dann den Katalog.

Worauf achten

Die Teile können in falscher Reihenfolge ankommen, einzelne können fehlen oder doppelt eintreffen. Der Empfänger muss also puffern, sortieren und nach einer Wartezeit entscheiden, was mit unvollständigen Folgen passiert. Das Sortieren übernimmt der Resequencer, das Zusammenfügen der Aggregator aus Gruppe 5. Oft ist es einfacher, die Daten abzulegen und nur einen Verweis zu verschicken (Claim Check, Gruppe 6).

In der Praxis

Die JMS-Gruppierung mit den Eigenschaften JMSXGroupID und JMSXGroupSeq, das Paar Splitter und Aggregator in Camel und Spring Integration mit den Headern sequenceNumber und sequenceSize, Multipart-Uploads bei Cloud-Speichern.

27. Message Expiration · Ablaufzeit

BörseKurs-Appbis 12:05bis 12:09bis 12:10Dead Letter ChannelAblaufzeit im Header jeder Nachrichtjetzt: 12:07Kurs von 12:04ist wertlos
Ein Kurs, der um 12:05 Uhr abgelaufen ist, wird nicht mehr angezeigt, sondern aussortiert.

Die Idee

Manche Nachrichten sind nur für eine begrenzte Zeit sinnvoll. Der Sender gibt ihnen deshalb eine Ablaufzeit mit, entweder als festen Zeitpunkt oder als Lebensdauer. Ist sie überschritten, bevor die Nachricht zugestellt wurde, wird sie nicht mehr ausgeliefert. Das Messaging-System verwirft sie oder legt sie in den Dead Letter Channel.

Beispiel

Eine Börse verschickt Aktienkurse mit einer Gültigkeit von einer Minute. Die Kurs-App war kurz offline und meldet sich um 12:07 Uhr zurück. Der Kurs von 12:04 Uhr ist inzwischen abgelaufen und wird nicht mehr zugestellt. Die App zeigt nur aktuelle Kurse an, statt erst einen Berg veralteter Daten abzuarbeiten.

Worauf achten

Die Ablaufzeit schützt vor zwei Problemen: davor, dass Empfänger veraltete Informationen verarbeiten, und davor, dass sich bei einem Ausfall ein riesiger Rückstau bildet. Zeitangaben setzen voraus, dass die Uhren aller beteiligten Server ausreichend gleich gehen. Die Ablaufzeit greift nur bis zur Zustellung. Hat der Empfänger die Nachricht schon erhalten, muss er selbst prüfen, ob sie noch gültig ist.

In der Praxis

setTimeToLive() bzw. das Feld JMSExpiration in JMS, die Eigenschaft expiration pro Nachricht und x-message-ttl pro Queue in RabbitMQ. Typische Einsatzgebiete sind Kurse, Positionsdaten, Einmalpasswörter und zeitkritische Benachrichtigungen.

28. Format Indicator · Formatkennzeichen

Shop (alt)Shop (neu)format: v1format: v2Lagerv1: menge in Stückv2: menge + einheitwählt je nach format
Alte und neue Version des Shops schicken unterschiedliche Formate. Das Lager erkennt am Header, wie es lesen muss.

Die Idee

Nachrichtenformate ändern sich mit der Zeit: Felder kommen hinzu, werden umbenannt oder anders aufgebaut. Damit ein Empfänger weiß, wie er eine Nachricht lesen muss, trägt sie ein Formatkennzeichen, meist eine Versionsnummer im Header. Der Empfänger liest zuerst das Kennzeichen und wählt dann den passenden Weg, den Inhalt zu verarbeiten.

Beispiel

Der Shop wird schrittweise auf eine neue Version umgestellt. In Version 2 hat die Bestellung zusätzlich eine Mengeneinheit, damit auch Meterware verkauft werden kann. Eine Zeit lang laufen alte und neue Shop-Server parallel. Das Lager liest das Feld format und verarbeitet v1-Nachrichten wie bisher, v2-Nachrichten mit Einheit.

Worauf achten

Das Buch nennt drei Varianten: eine Versionsnummer, einen Verweis auf eine Formatbeschreibung (etwa eine Schema-ID) oder eine Formatangabe im Dokument selbst (wie die Namespace-Angabe in XML). Ohne Kennzeichen bleibt dem Empfänger nur, das Format zu erraten. Das funktioniert, bis zwei Versionen sich zu ähnlich sind. Gut ist es außerdem, Formate möglichst abwärtskompatibel zu ändern, etwa nur optionale Felder hinzuzufügen, damit alte Empfänger neue Nachrichten trotzdem verstehen.

In der Praxis

Schema-IDs in der Confluent Schema Registry für Avro und Protobuf, der Content-Type-Header mit Versionsangabe wie application/vnd.firma.bestellung.v2+json, die Eigenschaft type in CloudEvents und Namespaces in XML.

Command, Document und Event im Vergleich

Command MessageDocument MessageEvent Message
Aussage„Tu das!“„Hier sind Daten.“„Das ist passiert.“
NameVerb im Imperativ: VersendeBestellungSubstantiv: Kunde, RechnungVergangenheit: BestellungEingegangen
Empfängergenau einer, der zuständig isteiner oder mehrerebeliebig viele, auch keiner
Typischer KanalPoint-to-PointPoint-to-Point oder Publish-SubscribePublish-Subscribe
Wichtig istdie Ausführungder Inhaltder Zeitpunkt
Der Sender ...erwartet, dass etwas passiertliefert, was gebraucht wirdweiß nicht, wer reagiert