Spring Integration provides an implementation of the Enterprise Integration Patterns (as described in the book “Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions” by Gregor Hohpe and Bobby Woolf). While these patterns are mostly designed to integrate separate heterogeneous systems, Spring Integration allows you to use these patterns inside of your application. One useful mechanism provided by Spring Integration is an implementation of the “Request-Reply” pattern. In this pattern a command message is sent with a “ReplyTo” address. Using the “ReplyTo” address, the replier entity knows where to send the response. This pattern is illustrated in the following diagram:
The plumbing to achieve this is somewhat complicated an tedious, however Spring Integration takes care of all of the boilerplate code, with just a few lines of XML. This provides a very powerful mechanism, where you can define your interface and use it, without any knowledge of the mechanism used to communicate with the actual implementation. Furthermore, you can change this mechanism as needed, just by changing your configuration.
In this post we’re going to wire a service using a direct connection. The direct connection occurs inside the JVM, in the same thread, resulting in very little overhead. We’re also going to configure that same service using a JMS queue. Using this transport mechanism, the Requestor and the Replier and decoupled, and can live in different JVMs, potentially hosted in different machines. This also allows you to scale, by adding more Replier machines, to increase the capacity.
Using Spring Integration to wire a service
In this first part, we’re going to create a an Interface for a service. The implementation of this interface is not wired directly, but rather provided by Spring Integration, using the configuration provided in the spring XML. The Interface is very basic as you can see below. When using Spring Integration it’s best to design simple Interfaces, with few, coarse grained methods.
This interface is going to be backed by the following class:
You might notice that OrderServiceHandler does not implement IOrderService, and in fact, the methods names do not even match. So how can we have such level of decoupling? This is where Spring Integration comes into the picture:
Let’s look at the relevant part of this Spring xml configuration (
- We instantiate an
OrderServiceHandlerbean. This will execute our business logic. How you instantiate it is your choice, we declare it manually in this example for clarity.
- We declare a Spring Integration channel
requestChannel. This channel will send the reply back to the requestor.
- The Service Activator is an endpoint, which connects a channel to Spring Bean. In this case we’re connecting
requestChannelchannel to our business logic bean. In this xml block we also declare that the
processOrdermethod will invoked for this Service Activator.
- The Gateway is another endpoint point, designed to expose a simple interface, while hiding all of the Spring Integration complexity. In this case we’re wiring our two channels, and exposing the gateway as
IOrderServiceinterface. This is the basic syntax for an interface with a single method, the xml get more complicated with additional methods as every method must be wired independently.
- We’re using
requestChannelto send out requests.
appbean is our test application.
We explicitly specified a channel to send the request (
requestChannel). So how does the JMS outgoing-gateway know where to send the reply? By default it sends it back to the requester, using a temporary anonymous channel, so Spring Integration gives us that functionality for free. If you have special needs, to can specify the reply channel. We’ll explore this in future postings.
The code for the App is shown below, and it creates 5 threads, processing an order in each thread, using the
IOrderService bean provided by Spring Integration (Remember, we have no implementation of
IOrderService anywhere in our source code):
The example is provided as a Maven application, and can be compiled command:
Once compiled, you can see it in action:
mvn exec:java -Dexec.mainClass="com.javaprocess.examples.integration.main.Main" -Dexec.args="direct"
direct will cause the main method to use the Spring configuration we were examining before. The output will look something like this:
Running in client mode Requesting order processing on thread: 13 Requesting order processing on thread: 14 Requesting order processing on thread: 15 Requesting order processing on thread: 16 Requesting order processing on thread: 17 Got order with id 100 on thread: 16 Got order with id 100 on thread: 15 Got order with id 100 on thread: 17 Got order with id 100 on thread: 13 Got order with id 100 on thread: 14 Order was requested by 16 and by processed by thread: 16 Order was requested by 15 and by processed by thread: 15 Order was requested by 14 and by processed by thread: 14 Order was requested by 17 and by processed by thread: 17 Order was requested by 13 and by processed by thread: 13
So what is happening in this example?
- Spring Integration has created a dynamic proxy that implements IOrderService. This proxy is exposed as Spring Bean with name
orderService. This is the bean is used from inside App.
orderServiceis connected using two channels to a method (
processOrder) in the
- The processing of the order is occurring in the same thread in which it is requested.
This can be seen in the following diagram:
Up to this point, Spring Integration seems to add very little value, and a lot of extra XML configuration. The same result could have been achieved by having
IOrderService and wiring the bean directly (even easier if using annotations). However the real power of Spring Integration will become evident on the next section.
Using JMS with Spring Integration
In this next section we’re going to have two separate Spring configuration, one for the Requestor, and one for the Replier. They exist in two different files, and we can run them in the same JVM, or in separate JVMs.
First we will examine the file for requestor (
- The outgoing channel is declared. This channel is used by the gateway to send its requests.
- The gateway is setup in a similar fashion as in the first example. You might have noticed that it lacks a setting for
default-reply-channel, this is on purpose, and will be explained shortly.
- The JMS outgoing gateway (notice we’re using the
int-jmsnamespace). This is the interface with the JMS broker, it will read messages from
outChanneland send them to the
- The app is the same as used in the first example. This way we show how you wire services differently, without rewriting any code.
The counter part to requestor, is the replier (
spring-int-server.xml). The file is shown below:
- A broker is configured to run in-process, on the JVM hosting the replier or server component. This is suitable for a proof of concept such as this. In production you will want to run a stand alone and properly tuned Broker.
- This channel will be used to send messages received from JMS, to the service activator
- A JMS inbound gateway will read messages from the
Queue. Of special interest in here is the number of concurrent consumers. This can be tweaked to set the number of threads that will process incoming messages.
- The service activator is configured to listen on the
inChannel. Similar to the way we defined
Gateway, we have omitted the
You can run both the requestor and the replier in the same JVM, by using this command:
mvn exec:java -Dexec.mainClass="com.javaprocess.examples.integration.main.Main" -Dexec.args="client server"
Running in this fashion will result in an input similar to this:
Running in client mode Requesting order processing on thread: 33 Requesting order processing on thread: 34 Requesting order processing on thread: 35 Requesting order processing on thread: 36 Requesting order processing on thread: 37 Got order with id 100 on thread: 19 Got order with id 100 on thread: 24 Got order with id 100 on thread: 26 Got order with id 100 on thread: 18 Got order with id 100 on thread: 25 Order was requested by 37 and by processed by thread: 26 Order was requested by 36 and by processed by thread: 18 Order was requested by 34 and by processed by thread: 25 Order was requested by 33 and by processed by thread: 19 Order was requested by 35 and by processed by thread: 24 Closing context
Notice how the the thread that made the request is different from the thread the replied to the request.
We can take this a step further and see it run in two different JVMs. First start the server. This is necessary since it also starts the JMS broker that the client will connect to. Invoked in this fashion, the server will run until manually terminated, fulfilling any requests put in the queue.
mvn exec:java -Dexec.mainClass="com.javaprocess.examples.integration.main.Main" -Dexec.args="server"
Once the server has started, open a new terminal or command prompt window, and start the client:
mvn exec:java -Dexec.mainClass="com.javaprocess.examples.integration.main.Main" -Dexec.args="client"
You will see the client making requests:
Running in client mode Requesting order processing on thread: 13 Requesting order processing on thread: 14 Requesting order processing on thread: 15 Requesting order processing on thread: 16 Requesting order processing on thread: 17 Order was requested by 15 and by processed by thread: 21 Order was requested by 16 and by processed by thread: 24 Order was requested by 14 and by processed by thread: 20 Order was requested by 13 and by processed by thread: 17 Order was requested by 17 and by processed by thread: 19 Closing context
You will also see the server fulfilling these requests:
Running in server mode Got order with id 100 on thread: 19 Got order with id 100 on thread: 17 Got order with id 100 on thread: 20 Got order with id 100 on thread: 24 Got order with id 100 on thread: 21
If you start more than one client working at a time, you will see the server replying to all the incoming requests.
So how is this working?
On the other hand we define a JMS Queue as the destination to send the request, but how does the replier know which channel to use to send the respond? The outgoing-gateway will create a temporary channel, and sets
ReplyTo JMS header to this temporary channel. This can conceptualized in the following diagram:
So why should you try to use something like this? We’ll point out some general advantages, but as usual, remember that the best solution depends on your particular needs.
- This approach provides a deep decoupling, allowing you to have servers in the back servicing requests. These servers can be even added on demand.
- The scalability of Messaging based service buses tends to be better than other approaches like load balances, specially under heavy load. If using a load balancer, a single request that takes too long could cause a server to drop off the load balancer. With messaging, the available servers will continue to service requests as fast as possible.
- Spring Integration provides a great deal of extra functionality out of the box, such as the “Claim Check” pattern. We’ll examine some of these possibilities in future posts. Other remoting frameworks (such as plain Spring remoting) do not provide this wealth of extra functionality.
- This approach provides a huge deal of flexibility. Can even wire different methods in single interface using different transports (JMS, direct connection, REST web services).
- The configuration can be easily changed by modifying the XML files. These files can even be located in files outside of the application. This allows precise tuning, without having to change the application code, recompile, or repackage. In this respect, I favor using the XML configuration as opposed to Java configuration.
You can find the source code for this example in github.
To check out the code clone the following repository:
git clone https://github.com/aolarte/spring-integration-samples.git git checkout branches/part1