Overview
JMS is most frequently used in an asynchronous way, yet, there are times when we may need to leverage JMS in a synchronous way – we’ll go over some use cases later in this document.
In order for Request and Reply to work properly, a Correlation ID must be generated and attached to the event being sent and the same ID must be present on an event being received; this is how the event is mapped back to the original sender. Luckily, the correlation ID is automatically generated by the JMS Camel Component.
Why Use Request and Reply?
Synchronous Client
It may be the case that a user is going to wait for a response from your service. This implies that the user is a synchronous client; an example might be a user submitting an order and waiting for the order confirmation details.
Meanwhile, it is common to find backend services that only communicate using JMS or another asynchronous communication protocol. In this case, we need to be able to communicate with the other backend service using JMS, but we cannot do it in a completely async way because that would abandon the client request. Instead, we choose to use JMS with a ReplyTo attribute configured; this will hold a reference to the client HTTP session and generate a response once the reply is received.
In the example above, a user placed an order using HTTP, however, the other features that Order Service needs to communicate with are using JMS. Since the user is expecting a response and we need the chance to tell the user if the payment was a success, a request/reply JMS pattern is used. See a more detailed explanation of the request and reply with JMS below.
Batch Processing
When doing batch processing, it’s important to return a result to the client, but, it’s also important to optimize our backend resources to reduce bottlenecks. Batch processing in this context is defined as any process where each request could take several seconds to complete while others take a fraction of a second, depending on the data to process. An easy way to think of this is by organizing a basket of fruit:
If you receive a basket of fruit to sort (client request to process), a very full basket takes longer to sort whereas a nearly empty basket takes less time. If there are two people counting (HTTP thread (1) and processing service thread (2)) it would waste time and resources to take the large basket to be sorted and wait for it to be done before getting the smaller basket from the second client (new request). Instead, it would be better to take the large basket to be sorted, then, go get the next basket right away and deliver it to be sorted while waiting for the larger basket to be done.
In the example above, by making the call to the processor async (using JMS), we are freeing the initial thread to do other work while still ensuring that all the required work is getting done.
Reply Scope
The pattern that we’ve designed in Leap gives us the ability to control the scope of services that can leverage the JMS request/reply. As a general rule, only services lower in the routing hierarchy can directly interact with this pattern.
I know, that sounds confusing, but, it’s quite simple – as you’ll see in the examples later in the document. Essentially, if a queue is configured in the base route, then any service can call it; but, if a queue is configured in an execution route, only the implementation routes belonging to that execution should call the queue.
By writing our JMS request/reply directly in a Camel route, we gain the ability to call it directly from other parts of the project.
How To Implement ReplyTo Queue?
Base Route
By default, we have a request and reply queue defined in the baseroute, the route is called: direct:baseJMSRequestResponseQueue
. Any execution or implementation route can call this route in order to have the exchange body published to the generic requestQueue
where any consumer can take the exchange and send the response back to the generic responseQueue
. Once the response comes back or an error is generated, the result is sent back to the calling route. See the full endpoint definition below.
<to uri="jms:queue:requestQueue?requestTimeout=4s&exchangePattern=InOut&replyTo=responseQueue&jmsMessageType=Text&replyToType=Exclusive"></to>
Execution Route
An execution route does not contain a request and response JMS definition by default. However, you would use the same recipe as the baseroute. The main difference is that when you define a new request/reply JMS route, you MUST use a unique queue for the request and a unique replyTo
queue – if they match the names in the base route, this will cause event routing conflicts.
Implementation Route
If you know that only one feature will ever need to use the request and response queues you are defining, it is a good practice to define the JMS producer in the feature implementation route.
By doing this, you are manually setting the intent that this should only be used by one specific feature.
Examples
<route> <from uri="direct:baseJMSRequestResponseQueue"></from> <doTry> <to uri="jms:queue:requestQueue?requestTimeout=4s&exchangePattern=InOut&replyTo=responseQueue&jmsMessageType=Text&replyToType=Exclusive"></to> <doCatch> <exception>org.apache.camel.ExchangeTimedOutException</exception> <handled> <constant>true</constant> </handled> <camel:bean ref="responseHandler" /> <setHeader headerName="Content-Type"> <constant>application/vnd.leap+json</constant> </setHeader> <to uri="bean:leapEndpoint?method=getResponseForLeapEndpoint" /> <setHeader headerName="Content-Type"> <constant>application/json</constant> </setHeader> </doCatch> </doTry> </route>
<route> <from uri="direct:myImpl"></from> <to uri="direct:baseJMSRequestResponseQueue"></to> <to uri="bean:buildResponseBean?method=jmsResponseBuilder"></to> </route>
<route> <from uri="direct:myExecutionRouteRequestResponse"></from> <doTry> <to uri="jms:queue:customExecRequestQueue?requestTimeout=4s&exchangePattern=InOut&replyTo=customExecResponseQueue&jmsMessageType=Text&replyToType=Exclusive"></to> <doCatch> <exception>org.apache.camel.ExchangeTimedOutException</exception> <handled> <constant>true</constant> </handled> <camel:bean ref="responseHandler" /> <setHeader headerName="Content-Type"> <constant>application/vnd.leap+json</constant> </setHeader> <to uri="bean:leapEndpoint?method=getResponseForLeapEndpoint" /> <setHeader headerName="Content-Type"> <constant>application/json</constant> </setHeader> </doCatch> </doTry> </route>