← Zur Uebersicht

JMS Request/Reply

Beispiele: ClaimSubmissionClient (novaris-claims-mq, Sender/Empfaenger der Antwort), ClaimIntakeMDB.sendReply(...) (novaris-policy-core, Empfaenger der Anfrage/Sender der Antwort).

JMS ist von Natur aus asynchron und einseitig - eine Nachricht geht von A nach B, fertig. Trotzdem braucht man haeufig ein synchrones Anfrage/Antwort-Verhalten ueber JMS (der Aufrufer will wissen, ob und wie seine Schadensmeldung verarbeitet wurde, bevor er weitermacht). Das Request/Reply-Muster baut das aus JMS-Bordmitteln nach.

Die drei Zutaten

  1. JMSReplyTo - der Sender traegt in die Anfrage ein, wohin die Antwort gehen soll: java request.setJMSReplyTo(replyQueue); Der Empfaenger liest das aus und weiss dadurch, wohin er antworten soll, ohne dass diese Adresse fest verdrahtet sein muss.

  2. JMSCorrelationID - eine vom Sender erzeugte eindeutige ID (hier: eine UUID), die in der Antwort unveraendert zurueckgespiegelt wird: java String correlationId = UUID.randomUUID().toString(); request.setJMSCorrelationID(correlationId); // ... beim Empfaenger: reply.setJMSCorrelationID(originalMessage.getJMSCorrelationID()); Notwendig, weil eine Antwort-Queue von vielen parallelen Anfragen gemeinsam genutzt werden kann (siehe naechster Abschnitt) - ohne Korrelations-ID koennte ein Aufrufer die Antwort auf die Anfrage eines anderen lesen.

  3. Ein synchroner, selektiver Empfang - der Sender wartet aktiv (mit Timeout) auf genau die Antwort mit seiner eigenen Korrelations-ID: java String selector = "JMSCorrelationID = '" + correlationId + "'"; MessageConsumer replyConsumer = session.createConsumer(replyQueue, selector); // ... Anfrage senden ... Message reply = replyConsumer.receive(replyTimeoutMillis); Der Message-Selektor filtert bereits auf Provider-Seite - der Consumer sieht nur Nachrichten mit genau dieser Korrelations-ID, alle anderen Antworten auf derselben Queue bleiben fuer andere Consumer/Selektoren sichtbar.

Reihenfolge ist wichtig: erst zuhoeren, dann fragen

ClaimSubmissionClient.submitClaim(...) legt den selektiven replyConsumer bewusst vor dem Versenden der Anfrage an. Waere die Reihenfolge umgekehrt, koennte (bei einem sehr schnell antwortenden Empfaenger, insbesondere bei einem im selben Prozess laufenden Test-Broker) die Antwort theoretisch schon eintreffen, bevor der Consumer ueberhaupt existiert - eine klassische Race Condition bei selbstgebautem Request/Reply.

Fest konfigurierte Antwort-Queue vs. TemporaryQueue

Dieses Projekt nutzt eine feste, administrierte Antwort-Queue (jms/novaris/claimsReplyQueue) mit Korrelations-ID-Filterung. Die Alternative - eine TemporaryQueue pro Anfrage - vermeidet die Notwendigkeit einer Korrelations-ID (jede Anfrage hat ihre eigene, exklusive Antwort-Adresse), ist aber an die Lebensdauer der erzeugenden Connection gebunden und in administrativ strengen WebSphere-/MQ-Landschaften seltener anzutreffen, weil jede temporaere Queue eine dynamisch erzeugte Verwaltungseinheit auf dem Queue-Manager ist. Siehe ClaimSubmissionClient-Javadoc fuer die vollstaendige Abwaegung.

Timeout: die Kehrseite von "synchron ueber asynchron"

replyConsumer.receive(replyTimeoutMillis) blockiert hoechstens bis zum Timeout - antwortet der Empfaenger gar nicht (System down, Nachricht verloren, Verarbeitung haengt), bekommt der Aufrufer nach Ablauf null zurueck. ClaimSubmissionClient uebersetzt das in eine LegacySystemUnavailableException (System Exception, siehe docs/10-ejb-konzepte/06-exceptions.md). Das ist der Preis fuer synchrones Verhalten auf einer asynchronen Grundlage: der Aufrufer muss explizit mit "keine Antwort" umgehen, ein reiner Methodenaufruf haette dieses Problem nicht (wuerde dafuer aber die Entkopplung von Sender und Empfaenger verlieren).

⌂ Cockpit