Connecting routes (Simple)
A Camel route is the minimum self contained unit of messaging logic. It has a beginning, one or many processing steps, an end, its own lifecycle, error handlers, and so on. Any integration application of a reasonable size consists of multiple routes working simultaneously to achieve a common goal. In many terms, routes are similar to objects in the object oriented world. Routes have responsibilities and also interact with their peers, hosted on the same CamelContext
or even a separate CamelContext
in the same JVM. In this tutorial, we are going to see how to connect routes with one another and other Java codes.
Getting ready
The complete source code for this tutorial is located under the project, camel-message-routing-examples/connecting-routes
.
How to do it...
Create a route using the timer component to generate a message per second. Add a processor to log the messages, and then send them to the next route created as follows:
<route> <from uri="timer://start?fixedRate=true&period=1000"/> <to uri="log://org.apache.camel.generator?level=INFO"/> <to uri="direct:logger"/> </route>
Then, let's create a route that receives messages from the previous route using the direct consumer and logs them.
<route> <from uri="direct:logger"/> <to uri="log://org.apache.camel.logger?level=INFO"/> <to uri="mock:result"/> </route>
How it works...
Typical reasons for splitting a routing logic into multiple smaller routes are transaction boundaries, different error handling strategies for different parts of the application, code reuse, testability, and so on. In our example, we created two routes only to demonstrate the direct component usage. Direct component, as the name suggests, provides direct synchronous invocation of the consumer when a message is sent to that endpoint using a direct producer. Each direct endpoint is identified by a unique name in CamelContext
, and it can have only one consumer that receives messages and one or multiple producers. In our example, route one sends messages to direct:logger
using a direct producer, and route two consumes from the same endpoint.
Using direct component allows connecting two routes as if they were one route: the message flows through all processors of the first route, then the second one and returns control back to the consumer of the first route for the next message. Because it is synchronous invocation (literally direct producer invokes the direct consumer method) and uses the same thread for processing the messages in both routes, it is also possible to make both routes participate in the same transaction when a transacted route is created.
Another thing to notice is that we started using mock component. Mock endpoints collect all the exchanges they receive and allow us write tests and verify the routing logic. In a real word application, instead of having mock endpoints hardcoded in the route we would have other endpoints for further processing the messages and replace them with mock endpoints during testing. We can see how mock endpoints are used by looking at the unit tests accompanying each route, and we will also demonstrate how to use them in the later part of this book.
There's more...
In addition to synchronous route connections, Camel also lets us connect routes asynchronously, connect routes which are in different CamelContexts
, and also exchange messages with routes from custom Java code.
Asynchronous connection
Another option for connecting routes is to use the SEDA (Staged Event Driven Architecture) component. SEDA provides asynchronous behavior, where messages are exchanged using an inmemory BlockingQueue
. When this component is used, the producer sends a message and instantly returns to process the next message while letting the second route to process the messages using its own thread pool. This decoupling of routing threads allows us to implement patterns such as Competing Consumers (by setting the concurrentConsumers
option to a number greater than 1) or a Publish-Subscribe pattern (by setting the multipleConsumers
option to true
). With the Competing Consumers pattern, a message is delivered to only one from multiple consumers, allowing concurrent processing, whereas with Publish-Subscribe patterns each consumer receives a copy of the published messages. This component is ideal for situations when there is a long running task, and to prevent from blocking the whole route, we could create a separate route for the long running task and connect it to the main route using a SEDA component. With this approach, long running tasks will be processed by a separate thread on the subroute keeping the main route unblocked.
One important concept in Camel, which is also related to the SEDA component, is the message exchange pattern. Camel messages are either one way event messages (InOnly) or request reply (InOut). By default, many components use an InOnly exchange pattern, but we can specify the exchange pattern explicitly as part of the endpoint invocation:
from("activemq:someQueue") .inOnly("seda:nextRoute");
We can also specify it as a part of the route:
from("mq:someQueue") .setExchangePattern(ExchangePattern.InOut) .to("mock:result");
With one way messaging (which is also known as fire and forget) the sender doesn't expect a response from the receiver, whereas with request reply style messaging a response is expected. The Exchange
object keeps track of MEP and handles one way messages with its In Message
field, and uses Out Message
when a response is expected.
We can see the different ways for explicitly specifying the exchange pattern at http://camel.apache.org/request-reply.html.
The SEDA component will behave differently depending on the MEP. If the MEP is InOnly, the SEDA producer puts the message to the queue and continues with the next message without waiting for it to be processed. But, if the MEP is InOut, after passing the message it will block and wait for it to be processed (by a different thread from the SEDA consumer) or timeout occurs. This default behavior can be controlled with the waitForTaskToComplete
option, where setting it to Always
will make it always wait forthe exchange to be processed and setting a value to Never
will prevent it from waiting, regardless of the messaging pattern. We can see all the options available to the SEDA component at http://camel.apache.org/seda.html.
Connecting routes in the same JVM
Direct and SEDA components connect routes running within the same CamelContext
. To connect routes running in separate CamelContexts
, but in the same JVM, there are two other components: vm and direct-vm. The vm component extends SEDA, and direct-vm extends direct to provide communication across the CamelContext
instances. They can connect, for example, separate Camel application bundles running in the same OSGI container or separate war files running in the same web-container such as Tomcat. Internally these components are implemented using static fields, so it is important that the applications share the same camel-core.jar
on their classpath. With an OSGI container, this is done by having one version of camel-core used by Camel applications wanting to communicate and with Tomcat it is achieved by placing the camel-core.jar
file in the ext
directory as opposed to each WAR file.
Calling routes from Java methods
Routes can send and receive messages among them, but it is also possible to do the same from our Java code. To send a message to a route, create a ProducerTemplate
(named after Spring templates) from CamelContext
, and then send messages to the consumer using direct component:
ProducerTemplate producerTemplate = camelContext.createProducerTemplate(); template.sendBody("direct:logger", "<hello>world!</hello>");
Notice that with producerTemplate
we can send messages directly to any endpoint even if they are not part of a route. For example, the following line of code will send the message directly to an ActiveMQ
queue without a need for a route:
template.sendBody("activemq:my.queue", "<hello>world!</hello>");
ProducerTemplate
has mainly two kinds of methods: send*
method which performs fire and forget style InOnly messages, and also request*
method which do request-reply style InOut messages returning the processed result.
In a similar fashion, there is also ConsumerTemplate
for receiving messages from an endpoint:
ConsumerTemplate consumerTemplate = camelContext().createConsumerTemplate(); String result = consumerTemplate.receiveBody("activemq:my.queue", String.class);
The consumerTemplate
in the previous code snippet will retrieve a message from ActiveMQ
and return the message body as string.