Wednesday, June 25, 2008

Trying out Spring Integration

When building our Virtual Enterprise (1-1.5 years ago), we ended up using Mule ESB (version 1.3.3 at the time) to handle protocol mediation. The intent at the time was to focus on getting our hands into various SOA product suites in more of a real world situation. Therefore, we really didn't want to spend the time to write the code required to write to or read from a JMS queue, or call a web service, or write code to communicate over whatever protocol our enterprise requires. We also wanted the ability to change the protocol over which any endpoint within the Virtual Enterprise communicated. This would allow us to mimic numerous scenarios without having to make coding changes. Mule ESB gave us these capabilities mainly through the configuration of connectors and routers on POJOs.

With the introduction of Spring Integration (currently milestone 4), I have decided to take a look back at one of the endpoints in our Virtual Enterprise and convert it from Mule to Spring Integration. The main motivation for this is to get my hands into Spring Integration and start to get a feel for the capabilities it offers. The endpoint I chose was the Emergency Alert System (EAS). This is a spring web application that runs an embedded instance of Mule for the protocol mediation mentioned above. This application communicates via message queues. When an alert is created, it sent to a JMS queue that kicks off a business process which subsequently notifies other external systems. During various points in the business process, information flows back into the EAS via other message queues.

In order to accomplish this with Mule, the following mule configuration file was required:


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mule-configuration PUBLIC "-//SymphonySoft //DTD mule-configuration XML V1.0//EN"
"http://www.symphonysoft.com/dtds/mule/mule-spring-configuration.dtd">

<mule-configuration id="SOA_LAB_EAS" version="1.0">

<mule-environment-properties serverUrl="tcp://localhost:60505"/>

<!-- Use this connector if using ActiveMQ as the JMS provider -->
<connector name="jmsConnector" className="org.mule.providers.jms.JmsConnector">
<properties>
<property name="specification" value="1.1"/>
<property name="connectionFactoryJndiName" value="ConnectionFactory"/>
<property name="jndiInitialFactory" value="org.apache.activemq.jndi.ActiveMQInitialContextFactory"/>
<map name="connectionFactoryProperties">
<property name="brokerURL" value="tcp://localhost:61616"/>
<property name="brokerXmlConfig" value="classpath:/activemq-config.xml"/>
</map>
</properties>
</connector>

<transformers>
<transformer name="JmsMessageToString" className="org.mule.providers.jms.transformers.JMSMessageToObject" returnClass="java.lang.String"/>
<transformer name="StringToJmsMessage" className="org.mule.providers.jms.transformers.ObjectToJMSMessage" returnClass="javax.jms.TextMessage"/>
</transformers>

<model name="EAS">
<mule-descriptor name="EASCallReceiver" implementation="conferenceCallReceiver">
<inbound-router>
<endpoint address="jms://alert.inbound.call.queue" transformers="JmsMessageToString"/>
</inbound-router>
</mule-descriptor>
<mule-descriptor name="EASNotificationReceiver" implementation="notificationInfoReceiver">
<inbound-router>
<endpoint address="jms://alert.inbound.notification.queue" transformers="JmsMessageToString"/>
</inbound-router>
</mule-descriptor>
</model>

</mule-configuration>

As you can see, this configuration defines the appropriate JMS Connection information for connecting to ActiveMQ, the transformers necessary to convert to and from JMS Text Messages, and the Mule endpoints (the implementations refer to Spring beans defined in my Spring config file) with the appropriate routers defined for the inbound and outbound JMS Queues. This configuration isn't too bad. In order to embed Mule within my Spring application, I had to add 16 JARS to my classpath. Then, when the web application would start up, the embedded Mule server would start up as well, sharing the spring context, and everything connected nicely. In order to send messages to the outbound queue, I used the MuleClient class to get access to the endpoints, and let the Mule server handle the rest. The code was as simple as this:

MuleClient muleClient = new MuleClient();
muleClient.sendNoReceive(outboundEndpoint, messageBody, null);

where outboundEndpoint is a String value injected into my class which references the URI for the JMS queue (i.e. jms://alert.outbound.queue). As for the inbound messages, they were handled by Mule based on the inbound routers configured on the Mule Endpoints. Once a message was delivered to a queue, Mule transformed it to a string (based on the defined transformers) and routed it to my POJO. Overall, we were quite pleased with this scenario.

With the introduction of Spring Integration, we were interested in seeing if we could use this framework in the same way we were using Mule. So I went about the task of converting the embedded Mule configuration to Spring Integration. In the end, it really wasn't too difficult.

The first step in my conversion process was to send the outbound message to the alert.outbound.queue (an ActiveMQ queue). I only had to add the following JARS to my classpath: org.springframework.integration-1.0.0.M4.jar and org.springframework.integration-adapter-1.0.0.M4.jar.

In order to use spring integration to route my messages, I first defined a message bus. This is accomplished by declaring the following component in my spring configuration file:

<integration:message-bus/>
Next, I needed to define a channel, and connect that channel to a target endpoint. This is accomplished with the following configuration:

<integration:channel id="outboundAlertChannel"/>
<integration:target-endpoint input-channel="outboundAlertChannel" target="jmsTarget"/>

Since we are targeting a JMS endpoint, the target attribute refers to a JMS target bean configured like this:

<integration:jms-target id="jmsTarget" connection-factory="jmsFactory" destination-name="alert.outbound.queue"/>
This uses an ActiveMQ connection factory and defines the queue destination on which to place the outbound message.

Then in my POJO, I added setters to inject the MessageChannel (represented by the channel element in my configuration) so I could send the message to the channel.

outboundAlertChannel.send(new StringMessage(messageBody));

Then the Spring Integration framework would take over from there to route the message to the alert.outbound.queue.

Next, I had to configure the inbound messages and route them to a particular method on another POJO. This was pretty straightforward as well, and was accomplished entirely through configuration. I needed to define a channel, a JMS Source, and a handler endpoint. In this case, I wanted to follow a message driven approach rather than a polling approach, so I used a JMS Gateway. Unfortunately, there is a bug when using namespace support for JMS Gateway configuration (that is already fixed, but not in milestone 4), so I had to configure it using a bean declaration. The configuration for receiving the message from the queue and routing to my POJO can be seen below:


<integration:channel id="inboundAlertNotificationChannel"/>

<bean id="jmsGateWay" class="org.springframework.integration.adapter.jms.JmsGateway" init-method="start"
destroy-method="destroy">
<property name="connectionFactory" ref="jmsFactory"/>
<property name="requestChannel" ref="inboundAlertNotificationChannel"/>
<property name="destinationName" value="alert.inbound.notification.queue"/>
<property name="expectReply" value="false"/>
</bean>

<integration:handler-endpoint input-channel="inboundAlertNotificationChannel" handler="notificationInfoReceiver" method="receiveNotificationInfo"/>

The jmsGateway connects the queue to the channel and the handler-endpoint connects the channel to my POJO, specifying the method to call on the POJO. The message is converted by the framework from a JMS Text message to a String. That was it, no coding changes required for this modification.

I found using Spring Integration (in this simple example) very logical, straightforward, and lightweight. I guess these seem to be typical attributes that come to mind when using Spring.