Chapter 9. Spring Integration and the Java Message Service

 

This chapter covers

  • How Spring Integration and JMS fit together
  • Sending and receiving JMS messages with Spring
  • JMS gateways and channel adapters

 

For many Java developers, the first thing that comes to mind when they hear “messaging” is the Java Message Service (JMS). That’s understandable considering it’s the predominant Java-based API for messaging and sits among the standards of the Java Enterprise Edition (JEE). The JMS specification was designed to provide a general abstraction over message-oriented middleware (MOM). Most of the well-known vendor products for messaging can be accessed and used through the JMS API. A number of open source JMS implementations are also available, one of which is ActiveMQ, a pure Java implementation of the JMS API. We use ActiveMQ in some of the examples in this chapter because it’s easy to configure as an embedded broker. We don’t go into any specific ActiveMQ details, though. If you want to learn more about it, please refer to ActiveMQ in Action by Bruce Snyder, Dejan Bosanac, and Rob Davies (Manning, 2011).

Hopefully, by this point in the book, you realize that messaging and event-driven architectures don’t necessarily require the use of such systems. We’ve discussed messaging in several chapters thus far without having to dive into the details of JMS, which reveals that Spring Integration can stand alone as a framework for building messaging solutions. In a simple application with no external integration requirements, producer and consumer components may be decoupled by message channels so that they communicate only with messages rather than direct invocation of methods with arguments. Messaging is really a paradigm; the same underlying principles apply whether messaging occurs between components running within the same process or between components running under different processes on disparate systems.

Nevertheless, by supporting JMS, Spring Integration provides a bridge between its simple, lightweight intraprocess messaging and the interprocess messaging that JMS enables across many MOM providers. In this chapter, you learn how to map between Spring Integration messages and JMS messages. You also learn about several options for integrating with JMS messaging destinations. Spring Integration provides channel adapters and gateways as well as message channel implementations that are backed by JMS destinations. In many cases, the configuration of these elements is straightforward. But, to get the most benefit from the available features, such as transactions, requires a thorough understanding of the underlying JMS behavior as dictated by the specification. Therefore, in this chapter, we alternate between the Spring Integration role and the specific JMS details as necessary.

9.1. The relationship between Spring Integration and JMS

Spring Integration provides a consistent model for intraprocess and interprocess messaging. The primary role of channel adapters and messaging gateways is to connect a local channel to some external system without impacting the producer or consumer components’ code. Another benefit the adapters provide is the separation of the messaging concerns from the underlying transports and protocols. They enable true document-style messaging whether the particular adapter implementation is sending requests over HTTP, interacting with a filesystem, or mapping to another messaging API. The JMS-based channel adapters and messaging gateways fall into that last category and are therefore excellent choices when external system integration is required. Given that the same general messaging paradigm is followed by Spring Integration and JMS, we can conceptualize the intraprocess and interprocess components as belonging to two layers but with a consistent model. See figure 9.1.

Figure 9.1. The top configuration shows interprocess integration using JMS. The bottom configuration shows intraprocess integration using Spring Integration. Which type of integration is appropriate depends on the architecture of the application.

Even though we tend to focus on external system integration when discussing the roles of JMS, there are also benefits to using JMS internally within an application. JMS may be useful between producers and consumers running in the same process because a JMS provider can support persistence, transactions, load balancing, and failover. For this reason, Spring Integration provides a message channel implementation that delegates to JMS behind the scenes. That channel looks like any other channel as far as the message-producing and message-consuming components are concerned, so it can be used as an alternative at any point within a message flow as shown in figure 9.2.

Figure 9.2. Design of the destination-backed channel of Spring Integration. It benefits from the guarantees supported by a JMS implementation, but it hides the JMS API behind a channel abstraction.

Even for messaging within a single process, the use of a JMS-backed channel provides several benefits. Consider a message channel backed by a simple in-memory queue, as occurs when using the <queue> subelement within a <channel> without referencing any MessageStore. By default, such a channel doesn’t persist messages to a transactional resource. Instead, the messages are only stored in a volatile queue such that they can be lost in the case of a system failure. They’ll even be lost if the process is shut down intentionally before those messages are drained from the queue by a consumer. In certain cases, when dealing with real-time event data that doesn’t require persistence, the loss of those event messages upon process termination might not be a problem. It may be well worth the trade-off for asynchronous delivery that allows the producer and consumer to operate within their own threads, on their own schedules. With these message channels backed by a JMS Destination, though, you can have the best of both worlds. If persistence and transactions are important, but asynchronous delivery is also a desired feature, then these channels offer a good choice even if they’re only being used by producers and consumers in the same process space.

The main point here is that even though we often refer to JMS as an option for messaging between a number of individual processes, that’s not the only time to consider JMS or other interprocess broker-based messaging solutions, such as Advanced Message Queuing Protocol (AMQP), as an option. When multiple processes are involved, the other advantages become more evident. First among these is the natural load balancing that occurs when multiple consuming processes are pulling messages from a shared destination. Unlike producer-side load balancing, the consumers can naturally distribute the load on the basis of their own capabilities. For example, some processes may be running on slower machines or the processing of certain messages may require more resources, but the consumers only ask for more messages when they can handle them rather than forcing some upstream dispatcher to make the decisions.

The second, related benefit is increased scalability. Message-producing processes might be sending more messages than a single consuming process can handle without creating a backlog, resulting in a constantly increasing latency. By adding enough consuming processes to handle the load, the throughput can increase to the point that a backlog no longer exists, or exists only within acceptable limits in rarely achieved high-load situations that occur during bursts of activity from producers.

The third benefit is increased availability. If a consuming process crashes, messages can still be processed as long as one or more other processes are still running. Even if all processes crash, the mediating broker can store messages until those processes come back online. Likewise, on the producing side, processes may come and go without directly affecting any processes on the consuming side. This is nothing more than the benefit of loose coupling inherent in any messaging system, applied not only across producers and consumers themselves but the processes in which they run. Keep in mind when we discuss these scenarios where processes come and go, we’re not merely talking about unforeseen system outages. It’s increasingly common for modern applications to have “zero downtime” requirements. Such an application must have a distributed architecture with no tight coupling between components in order to accommodate planned downtime of individual processes, one at a time, for system migrations and rolling upgrades.

One last topic we should address briefly here is transactions. We revisit transactions in greater detail near the end of this chapter, but one quick point is relevant to the discussion at hand. In the scenario described previously, where a consuming process crashes or is taken offline while responsible for an in-flight message, transactions play an important role. If the consumer reads a message from a destination but then fails to process it, such as might occur when its host process crashes, then the message might be lost depending on how the system is configured. In JMS, a variety of options correspond to different points on the spectrum of guaranteed delivery. One configuration option is to require an explicit acknowledgment from the consumer. It might be that a consumer acknowledges each message after it successfully stores it on disk. A more robust option is to enable transactions. The consumer would commit the transaction only upon successful processing of the message, and it would roll back the transaction in case of a known failure. When this functionality is enabled, not only do the multiple consuming processes share the load, they can even cover for each other in the event of failures. One consumer may fail while a message is in flight, but its transaction rolls back. The message is then made available to another consuming process rather than being lost.

Table 9.1 provides a quick overview of the benefits of using JMS with Spring Integration.

Table 9.1. Benefits of using JMS with Spring Integration

Benefit

Description

Load balancing Multiple consumers in separate virtual machine processes pull messages from a shared destination at a rate determined by their own capabilities.
Scalability Adding enough consumer processes to avoid a backlog increases throughput and decreases response time.
Availability With multiple consumer processes, the overall system can remain operational even if one or more individual processes fail. Likewise, consumer processes can be redeployed one at a time to support a rolling upgrade.

It’s worth pointing out that the benefits listed in table 9.1 aren’t limited to JMS. Any broker that provides support for reliable messaging across distributed producer and consumer processes would provide the same benefits. For example, Spring Integration 2.1 adds support for RabbitMQ, which implements the AMQP protocol. Using the AMQP adapters would offer the same benefits. Likewise, although not as sophisticated, even using Spring Integration’s queue-backed channels along with a MessageStore can provide the same benefits because that too enables multiple processes to share the work. For now, let’s get back to the discussion at hand and explore the mapping of Spring Integration message payloads and headers to and from JMS message instances.

9.1.1. Mapping between JMS and Spring Integration messages

When considering interprocess messaging from the perspective of Spring Integration, the primary role of channel adapters is to handle all of the communication details so that the component on the other side of the message channel has no idea that an external system is involved. That means the channel adapter not only handles the communication via the particular transport and protocol being used but also must provide a Messaging Mapper (http://mng.bz/Fl0P) so that whatever data representation is used by the external system is converted to and from simple Spring Integration messages. Some of that data might map to the payload of such a message, whereas other parts of the data might map to the headers. That decision should be based on the role of the particular pieces of data, keeping in mind that the headers are typically used by the messaging infrastructure, and the payload is usually the business data that has some meaning within the domain of the application. Thinking of a message as fulfilling the document message pattern from Hohpe and Woolf’s Enterprise Integration Patterns (Addison-Wesley, 2003), the payload represents the document, and the headers contain additional metadata, such as a timestamp or some information about the originating system.

It so happens that the construction of a JMS message, according to the JMS specification, is similar to the construction of a Spring Integration message. This shouldn’t surprise you given that the function of the message is the same in both cases. It does mean that the messaging mapper implementation used by the JMS adapters has a simple role. We’ll go into the details in a later section, but for now it’s sufficient to point out that there are merely some differences in naming. In JMS, the message has a body, which is the counterpart of a payload in Spring Integration. Likewise, a JMS message’s properties correspond to a Spring Integration message’s headers. See figure 9.3.

Figure 9.3. Spring Integration and JMS messages in a side-by-side comparison. The terminology is different, but the structure is the same.

9.1.2. Comparing JMS destinations and Spring Integration message channels

By now you’re familiar with the various message channel types available in Spring Integration. One of the most important distinctions we covered is the difference between point-to-point channels and publish-subscribe channels. You saw that when it comes to configuration, the default type for a channel element in XML is point-to-point, and the publish-subscribe channel is clearly labeled as such. The JMS specification uses destination instead of message channel, but it makes a similar distinction. The two types of JMS Destination are Queues and Topics. A JMS queue provides point-to-point semantics, and a topic supports publish-subscribe interaction. When you use a queue, each message is received by a single consumer, but when you use a topic, the same message can be received by multiple consumers. See table 9.2 for the side-by-side comparison.

Table 9.2. Comparing enterprise integration patterns (EIP) to JMS

EIP

JMS

Message Channel Destination
Point-to-point channel Queue
Publish-subscribe channel Topic

Now that we’ve discussed the relationship between Spring Integration and JMS at a high level, we’re almost ready to jump into the details of Spring Integration’s JMS adapters. First, it’s probably a good idea to take a brief detour through the JMS support in the core Spring Framework. For one thing, the Spring Integration support for JMS builds directly on top of Spring Framework components such as the JmsTemplate and the MessageListener container. Additionally, the general design of Spring Integration messaging endpoints is largely modeled after the Spring JMS support. You should be able to see the similarities as we quickly walk through the main components and some configuration examples in the next section.

9.2. JMS support in the Spring Framework

The logical starting point for any discussion of the Spring Framework’s JMS support is the JmsTemplate. This is a convenience class for interacting with the JMS API at a high level. Those familiar with Spring are probably already aware of other templates, such as the JdbcTemplate and the TransactionTemplate. These components are all realizations of the Template pattern described in the Gang of Four’s Design Patterns: Elements of Reusable Object-Oriented Software (Gamma et al., Addison-Wesley, 1994). Each of these Spring-provided templates satisfies the common goal of simplifying usage of a particular API. One quick example should be sufficient to express this idea. First, we look at code that doesn’t use the JmsTemplate but instead performs all actions directly with the JMS API. Note that even a simple operation such as sending a text-based message involves a considerable amount of boilerplate code. Here’s a simple send-and-receive echo example:

public class DirectJmsDemo {

  public static void main(String[] args) {
    try {
      ConnectionFactory connectionFactory =
          new ActiveMQConnectionFactory("vm://localhost");
      Connection connection = connectionFactory.createConnection();
      connection.start();
      int autoAck = Session.AUTO_ACKNOWLEDGE;
      Session session = connection.createSession(false, autoAck);
      Destination queue = new ActiveMQQueue("siia.queue.example1");
      MessageProducer producer = session.createProducer(queue);
      MessageConsumer consumer = session.createConsumer(queue);
      Message messageToSend = session.createTextMessage("Hello World");
      producer.send(messageToSend);
      Message receivedMessage = consumer.receive(5000);
      if (!(receivedMessage instanceof TextMessage)) {
        throw new RuntimeException("expected a TextMessage");
      }
      String text = ((TextMessage) receivedMessage).getText();
      System.out.println("received: " + text);
      connection.close();
    }
    catch (JMSException e) {
      throw new RuntimeException("problem occurred in JMS code", e);
    }
  }

}

This code is about as simple as it can get when using the JMS API directly. ActiveMQ enables running an embedded broker (as you can see from the "vm://localhost" URL provided to the ConnectionFactory). Many JMS providers would be configured within the Java Naming and Directory Interface (JNDI) registry, and that would require additional code to look up the ConnectionFactory and Queue. Now, let’s see how the same task may be performed using Spring’s JmsTemplate:

public class JmsTemplateDemo {

  public static void main(String[] args) {
    ConnectionFactory connectionFactory =
        new ActiveMQConnectionFactory("vm://localhost");
    JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    jmsTemplate.setDefaultDestination(new ActiveMQQueue("siia.queue"));
    jmsTemplate.convertAndSend("hello world");
    System.out.println("received: " + jmsTemplate.receiveAndConvert());
  }

}

The code is much simpler, and it also provides fewer chances for developer errors. Any JMSExceptions are caught and converted into RuntimeExceptions in Spring’s JmsException hierarchy. The JMS resources, such as Connection and Session, are also acquired and released as appropriate. In fact, if a transaction is active when this send operation is invoked, and some upstream process has already acquired a JMS Session, this send operation is executed in the same transactional context. If you’ve ever worked with Spring’s transaction management for data access, this concept should be familiar to you. The idea is roughly the same. If one particular operation in the transaction throws an uncaught RuntimeException, all operations that occurred in that same transactional context are rolled back. If all operations are successful, the transaction is committed.

You probably also noticed that the template method invoked is called convertAnd-Send and that its argument is an instance of java.lang.String. There are also send() methods that accept a JMS Message you’ve created, but by using the convertAndSend versions, you can rely on the JmsTemplate to construct the Messages. The conversion itself is a pluggable strategy. The JmsTemplate delegates to an instance of the MessageConverter interface, and the default implementation (SimpleMessage-Converter) automatically performs the conversions shown in table 9.3.

Table 9.3. Default type conversions supported by SimpleMessageConverter

Type passed to SimpleMessageConverter

JMS Message type

java.lang.String TextMessage
byte[] BytesMessage
java.util.Map MapMessage
java.io.Serializable ObjectMessage

The receiveAndConvert method performs symmetrical conversion from a JMS Message. When used on the receiving end, the SimpleMessageConverter extracts the JMS message’s body and produces a result with the same mappings shown in the table (for example, TextMessage to java.lang.String).

Sometimes the default conversion options aren’t a good fit for a particular application. That’s why the MessageConverter is a strategy interface that can be configured directly on the JmsTemplate. Spring provides an object-to-XML (OXM) marshalling version of the MessageConverter that supports any of the implementations of Spring’s Marshaller and Unmarshaller interfaces within its toMessage() and from-Message() methods respectively. For example, an application might be responsible for sending and receiving XML-based text messages over a JMS queue, but the application’s developers prefer to hide the XML marshalling and unmarshalling logic in the template itself. Spring’s MarshallingMessageConverter may be injected into the JmsTemplate, and in turn, that converter can be injected with one of the options supported by Spring OXM, such as Java Architecture for XML Binding (JAXB).

It’s also possible to provide a custom implementation of the Marshaller and Unmarshaller interfaces or even a custom implementation of the MessageConverter. For example, you could implement the MessageConverter interface to create a BytesMessage directly from an object using some custom serialization. That same implementation could then use symmetrical deserialization to map back into objects on the receiving side. Likewise, you might implement the MessageConverter interface to map directly between objects and text messages where the actual text content is formatted using JavaScript Object Notation (JSON).

In this section, you learned how the JmsTemplate can greatly simplify the code required to do some basic messaging when compared with using the JMS API directly. The examples covered both sending and receiving, but the receive operations were synchronous. Before we discuss how Spring Integration can simplify things even further, we cover the support for asynchronous message reception in the Spring Framework, which provides the foundation upon which the most commonly used JMS adapters in Spring Integration are built.

9.3. Asynchronous JMS message reception with Spring

The polling consumer and event-driven consumer patterns both make appearances throughout this book. You saw in chapter 4 that even with simple intraprocess messaging in a Spring Integration–based application, each pattern has a role in accommodating various message channel options and the use cases that arise from those choices. When dealing with external systems, some transports and protocols are limited to the polling consumer pattern. The JMS model enables both polling and event-driven message reception. This section covers the reasons to consider the event-driven approach and how the Spring Framework supports it, ultimately with message-driven plain old Java objects (POJOs) that keep your code simple and unaware of the JMS API.

9.3.1. Why go asynchronous?

Earlier chapters made it clear that receiving messages is usually more complicated than sending them. Even though the receiving part of the JmsTemplate example looks as simple as the sending part, it’s important to recognize that in that example, the receive operation is synchronous. The JmsTemplate has a receiveTimeout property. The JmsTemplate receive operations return as soon as a message is available at the JMS destination or the timeout elapses, whichever occurs first. That means that if no message is available immediately, the operations may block for as long as indicated by the receiveTimeout value.

When relying on blocking receive operations, such JmsTemplate usage is an example of the polling consumer pattern. In an application in which an extremely low volume of messages is expected, polling in a dedicated background thread might be okay. But, as we mentioned earlier in the book, most applications using messaging would prefer to have event-driven consumers.

Support for event-driven consumers could be implemented on top of the simple polling receive calls, though all but the most naive implementations would quickly become complex. A proper and efficient solution requires support for concurrent processing of the received messages. Such a solution would also support transactions, and ideally, it would accommodate a thread pool that adjusts dynamically according to the volume of messages being received. Those same requirements apply to any attempt to adapt an inherently polling-based source of data to an event-driven one. Obviously, that’s a common concern for many components in Spring Integration. To understand the details of how the framework handles those requirements, be sure to read chapters 4 and 15.

As far as the JMS adapters are concerned, the crux of the problem is that the invocation of the JMS receive operation must be performed within the context of the transaction. Then, if the subsequent handling of the message causes a failure, that’s most likely a reason to roll back the transaction. Some other consumer may be able to process the message, or perhaps this same handler could handle the message successfully if retried after a brief delay. For example, some system that it relies on might be down at the moment but will be available again shortly. If a JMS consumer rolls back a transaction, then the message won’t be removed from the destination; that’s what enables redelivery. But if the Exception is thrown by the handler in a different thread, it’s too late: the JMS consumer has already performed its role, and by passing off the responsibility to an executor that invokes the handler in a different thread, it would’ve returned successfully after that handoff. It would be unable to react to a rollback based on something that happens later, downstream in the handler’s processing of the message content.

9.3.2. Spring’s MessageListener container

You’re probably convinced that implementing an asynchronous message-driven solution isn’t trivial. It’s the type of generic, foundational code that should be provided by a framework so developers don’t have to spend time dealing with the low-level threading and transaction management concerns. Spring provides this support for JMS with its MessageListener containers. Let’s look at a modified version of the earlier Hello World example. We use JmsTemplate for the sending side only. The message is received by a MessageListener asynchronously, and the DefaultMessageListener-Container handles all of the low-level concerns:

public class MessageListenerContainerDemo {

  public static void main(String[] args) {

    // establish common resources
    ConnectionFactory connectionFactory =
        new ActiveMQConnectionFactory("vm://localhost");
    Destination queue = new ActiveMQQueue("siia.queue");

    // setup and start listener container
    DefaultMessageListenerContainer container =
        new DefaultMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.setDestination(queue);
    container.setMessageListener(new MessageListener() {
      public void onMessage(Message message) {
        try {
          if (!(message instanceof TextMessage)) {
            throw new IllegalArgumentException("expected TextMessage");
          }
          System.out.println("received: " +
              ((TextMessage) message).getText());
        }
        catch (JMSException e) {
          throw new RuntimeException(e);
        }
      }
    });
    container.afterPropertiesSet();
    container.start();

    // send Message
    JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    jmsTemplate.setDefaultDestination(queue);
    jmsTemplate.convertAndSend("Hello World");
  }

}

The code shows how you can take advantage of asynchronous message reception by depending on Spring’s DefaultMessageListenerContainer to handle the low-level concerns. Nevertheless, you might be thinking we added a lot more code to the example, and we’re back to dealing with some JMS API code directly. For example, we provided an implementation of the JMS MessageListener interface, and we’re catching the JMSExceptions to convert into RuntimeExceptions ourselves. In the next section, we take things two steps further. First, we rely on Spring’s MessageListenerAdapter to decouple our code from the JMS API completely. Second, we refactor the example to use declarative configuration and a dedicated Spring XML schema. In other words, we demonstrate a message-driven POJO.

9.3.3. Message-driven POJOs with Spring

The code and configuration for asynchronous reception can be much simpler. One goal for simplification should be to reduce the dependency on the JMS API. Rather than having to create an implementation of the MessageListener interface, you can rely on Spring’s MessageListenerAdapter to handle that responsibility. It’s straightforward: the adapter implements the MessageListener interface, but it invokes operations on a delegate when a message arrives. That instance to which it delegates can be any object. The previous code could be updated with the following replacement for the registration of the listener:

container.setMessageListener(new MessageListenerAdapter(somePojo));

An even better option is to use configuration rather than code. Spring provides a jms namespace that supports the configuration of a container and adapter in a few lines of XML:

<jms:listener-container>
    <jms:listener destination="aQueue" ref="aPojo" method="someMethod"/>
</jms:listener-container>

Many configuration options are available on both the listener-container and listener elements, but the preceding example provides a glimpse of the simplest possible working case. The XML schema is well documented if you’d like to explore the other options. Our goal here is to provide a sufficient level of background information so you can appreciate that Spring Integration builds directly on top of the JMS support within the base Spring Framework. At this point, you should have a fairly good understanding of that support. We now turn our focus back to Spring Integration to see how it offers an even higher-level approach.

9.4. Sending JMS messages from a Spring Integration application

Now that you’ve seen the similarities between Spring Integration messages and JMS messages and learned about the core Spring support for JMS, you’re well prepared to look at the process of sending JMS messages from a Spring Integration application. As with most adapters in Spring Integration, a unidirectional channel adapter and a request/reply gateway are available. Because it’s considerably simpler, we begin the discussion with the unidirectional channel adapter.

Spring Integration’s outbound JMS channel adapter is a JMS message publisher encapsulated in an implementation of Spring Integration’s MessageHandler interface. That means it can be connected to any MessageChannel so that any Spring Integration Messages sent to that channel are converted into JMS Messages and then sent to a JMS Destination. The JMS Destination may be a Queue or a Topic, but from the perspective of this adapter implementation, that’s a configuration detail.

The main class involved in the one-way outbound JMS adapter is called JmsSendingMessageHandler. If you look at its implementation, you’ll see that it builds completely on the JMS support of the underlying Spring Framework. The most important responsibilities are handled internally by an instance of Spring’s JmsTemplate. Most of the code in Spring Integration’s adapter handles the various configuration options, of which there are many. As far as most users are concerned, even those configuration options are handled by the XML namespace support. In most cases, only a small subset of those options would be explicitly configured, but there are many options for handling more nuanced usage requirements. We walk through several of these in a moment, but first let’s look at a typical configuration for one of these adapters:

<int-jms:outbound-channel-adapter channel="toJMS"
    destination-name="samples.queue.fromSI" />

That looks simple enough, right? Hopefully so, and if you can rely on the defaults, then that’s all you need to configure. It’s literally adapting the Spring Integration toJMS channel so that it converts the messages into JMS Messages and then sends them to the JMS Queue named samples.queue.fromSI. If you want to use a Topic instead of a Queue, be sure to provide the pub-sub-domain attribute with a value of true, as in the following example:

<int-jms:outbound-channel-adapter channel="toJMS"
    destination-name="samples.topic.fromSI"
    pub-sub-domain="true" />

Sometimes it’s not practical to rely on just the name of the JMS destination. In fact, it’s common that the queues and topics are administered objects that developers should always access via JNDI lookups. Fortunately, you can rely on the Spring Framework’s ability to handle that. Instead of using the destination-name attribute, you could provide a destination attribute whose name is a reference to another object being managed by Spring. That other object could then be a result of a JNDI lookup. For handling that lookup, Spring provides a FactoryBean implementation called the JndiObjectFactoryBean. Although it’s perfectly acceptable to define that Factory-Bean instance as a low-level bean element, there’s XML namespace support for much more concise configuration options, as shown here:

<int-jms:outbound-channel-adapter channel="toJMS" destination="fromSI"/>

<jee:jndi-lookup id="fromSI" jndi-name="jms/queue.fromSI"/>

The functionality would be exactly the same. The difference is limited to the configuration. Access by name is often sufficient in development and testing environments, but JNDI lookups might be required for a production system. In those cases, you can manage the configuration excerpts appropriately by using import elements in the configuration or other similar techniques. The important factor is that you don’t need to modify any code to handle those different approaches for resolving JMS destinations.

Fortunately, the configuration of the ConnectionFactory and Destinations can be shared across both the sending and receiving sides. Likewise, for commonly configured references, such as these destinations, there is consistency between the inbound and outbound adapters. In the next section, we focus on the receiving side. We begin with the inbound channel adapter that serves as a polling consumer.

9.5. Receiving JMS messages in a Spring Integration application

When receiving JMS messages in a unidirectional way, there are two options. You can define an inbound channel adapter that acts a polling consumer or one that acts as an event-driven consumer. The polling option is configured with an <inbound-channel-adapter> element as defined in the JMS schema. It accepts a destination-name attribute for the JMS Queue or Topic. Its default DestinationResolver looks up the destination accordingly, and if you need to customize that behavior for some reason, you can provide a destinationresolver attribute with the bean name reference of your own implementation. The <inbound-channel-adapter> element also requires a poller unless you’re relying on a default context-wide poller element. Here’s a simple example of an inbound channel adapter that polls for a JMS message every three seconds:

<int-jms:inbound-channel-adapter id="pollingJmsInboundAdapter"
    channel="jmsMessages" destination-name="myQueue">
    <int:poller fixed-delay="3000" max-messages-per-poll="1"/>
</int-jms:inbound-channel-adapter>

Like the outbound version, if you’re specifying a topic name rather than a queue name, you should also provide the pub-sub-domain attribute with a value of true. If instead you want to reference a Queue or Topic instance, you can use the destination attribute in place of destination-name. This is a common practice when defining this adapter alongside Spring’s JNDI support, as shown previously in the outbound channel adapter examples. The following is an example of the corresponding inbound configuration:

<int-jms:inbound-channel-adapter id="pollingJmsInboundAdapter"
    channel="jmsMessages" destination="myQueue">
    <int:poller fixed-delay="3000" max-messages-per-poll="1"/>
</int-jms:inbound-channel-adapter>

<jee:jndi-lookup id="myQueue" jndi-name="jms/someQueue"/>

As mentioned earlier, polling is rarely the best choice when building a JMS-based solution. Considering that the underlying JMS support in the Spring Framework enables asynchronous invocation of a MessageListener as soon as a JMS message arrives, that’s almost always the better option. The only exceptions might be when you want to configure a poller to run infrequently or only at certain times of the day. If the poller is limited to run at certain times of the day, you’d most likely use the cron attribute on a poller element. Other than in those rare situations, the responsiveness will be better and the configuration will be simpler if you stick with Spring Integration’s message-driven-channel-adapter. The basic configuration will look the same, but there’s no longer a need to define a poller:

<int-jms:message-driven-channel-adapter id="messageDrivenAdapter"
    channel="jmsMessages" destination-name="myQueue"/>

It may seem odd that, unlike most adapters you’ve seen, the element doesn’t include inbound in its name. Considering it’s message-driven, it should be relatively clear that this channel adapter is reacting to inbound JMS messages that arrive at the given queue or topic. It sends those messages to the channel referenced by the channel attribute.

9.6. Request-reply messaging

The discussion and examples in this chapter have thus far focused on unidirectional channel adapters. On the sending side, we haven’t yet discussed the case where we might be expecting a reply, and on the receiving side, we haven’t yet discussed the case where we might be expected to send a reply. We saw that Spring Integration’s inbound JMS channel adapters can receive messages with either polling or message-driven behavior. On the other hand, the outbound channel adapter can be used to send messages to a JMS destination, be it a queue or a topic. In both cases, the Spring Integration message is mapped to or from the JMS message so that the payload as well as the headers can correspond to the JMS message’s body and properties respectively.

This section introduces Spring Integration’s bidirectional gateways for utilizing JMS in a request-reply model of interaction. Much of the functionality, such as mapping between the JMS and Spring Integration message representations, is the same. The difference is that these request-reply gateways are responsible for mapping in both directions. As with the unidirectional channel adapter discussions, we begin with the outbound side. Whereas earlier we could describe the outbound behavior as solely responsible for sending messages, in the gateway case, there is a receive as well, assuming that the JMS reply message arrives as expected. The simplest way to think of the outbound gateway is as a send-and-receive adapter.

9.6.1. The outbound gateway

In the simplest case, the outbound configuration will look similar to the outbound-channel-adapter configuration we saw earlier:

<int-jms:outbound-gateway request-channel="toJMS"
    reply-channel="jmsReplies"
    request-destination-name="examples.gateway.queue"/>

The request-channel and reply-channel attributes refer to Spring Integration MessageChannel instances. Any Spring Integration message that’s sent to the request channel will be converted into a JMS message and sent to the gateway’s request destination (in this context, destination always refers to a JMS component, and channel is the Spring Integration message channel). In this example, the destination is a queue. If it were a topic, the request-pub-sub-domain attribute would need to be provided with a value of true. Because the gateway must manage sending and receiving separately, many of its attributes are qualified as affiliated with either the request or the reply. The reply-channel is where any JMS reply messages are sent after they’re converted to Spring Integration messages.

You may have noticed that we didn’t provide a reply-destination-name. That attribute is optional, but it’s common to leave it out. The gateway implementation provides the JMSReplyTo property on each request message it sends to JMS. If you don’t provide a specific destination for that, then it’ll handle creation of a temporary queue for that purpose. This assumes that wherever these JMS messages are being sent, a process is in place to check for the JMSReplyTo property so it knows where to return a reply message. We discuss the server-side behavior in the next section when we cover the inbound gateway. For the time being, we discuss this interaction with a hypothetical server side where we assume such behavior is in place. The JMSReplyTo property is a standard part of the message contract and is defined in the JMS specification. Therefore, it’s commonly supported functionality for server-side JMS implementations that accept request messages from a sender who is also expecting reply messages. You must be sure that you’re sending to a destination that’s backed by a listener implementation with that behavior. The inbound channel adapter we discussed earlier would not be a good choice because, as we emphasized, it’s intended for unidirectional behavior only. On the other hand, the inbound gateway we discuss in the next section would be a valid option for such server-side request-reply behavior. The core Spring Framework support for message-driven POJOs also supports the JMSReplyTo properties of incoming messages as long as the POJO method being invoked has a nonvoid and non-null return value.

9.6.2. The inbound gateway

Like the outbound gateway, Spring Integration’s inbound gateway for JMS is an alternative to the inbound channel adapters when request-reply capabilities are required. Perhaps the quickest way to get a sense of what this means is to consider that this adapter covers the functionality we described in abstract terms as the server side in the previous section. The outbound gateway would be the client side as far as that discussion is concerned. The inbound gateway listens for JMS messages, maps each one it receives to a Spring Integration message, and sends that to a message channel. Thus far, that’s no different than the role of an inbound channel adapter. The difference is that the message channel in this case would be the initiating end of some pipeline that’s expected to produce a reply at some point downstream. When such a reply message is eventually returned to the inbound gateway, it’s mapped to a JMS message. The gateway then sends that JMS message to the reply destination. That particular JMS message fulfills the role of the reply message from the client’s perspective.

The reply destination is an example of the return address pattern. It may have been provided in the original message’s JMSReplyTo property, and if so, that takes precedence. If no JMSReplyTo property was sent, the inbound gateway falls back to a default reply destination, if configured. As with the request destination, that can either be configured by direct reference or by name. The attribute used for a direct reference is called default-reply-destination (again, it’s the default because the JMSReplyTo property on a request message takes precedence). If configuring the name of the reply destination so that it can be resolved by the gateway’s DestinationResolver strategy, use either the default-reply-queue-name attribute or the default-reply-topic-name attribute. If there’s neither a JMSReplyTo property on the request message nor a configured reply destination, then an exception will be thrown by the gateway because it would have no way of determining where to send the reply.

That description of the inbound gateway’s role probably sounds like it involves a complex implementation. Keep in mind that it builds directly on top of the underlying Spring JMS support that we described earlier. Now you can probably appreciate why we went into considerable detail about that underlying support. As a result, you already have a basic understanding of how the inbound gateway handles the server-side request-reply interaction. As with any Spring Integration inbound gateway, once it maps to a Spring Integration message, it sends that message to a message channel. What makes each gateway unique is what it receives and how it maps what it receives into a Spring Integration message.

Now that you understand the role of the inbound gateway for JMS, let’s look at an example. We start with the simplest configuration options:

<int-jms:inbound-gateway id="exampleGateway"
    request-destination-name="someQueue"
    request-channel="requestChannel"/>

The request-channel indicates where the inbound messages should be sent after they’re created by mapping from the JMS source message, and the request-destination-name is where those JMS messages are expected to arrive. You may have noticed that no reply-channel attribute is present. This attribute is an optional value for inbound gateways in general. If it’s not provided, then the gateway creates a temporary, anonymous channel and sets it as the replyChannel header for the message that it sends downstream. As with the <inbound-channel-adapter> for JMS, the connection-factory attribute is also optional, as long as a JMS ConnectionFactory instance is named connectionFactory in the application context. Add that attribute if for some reason the JMS ConnectionFactory you need to reference has a different bean name.

As you might expect, knowing that we’re building on top of Spring’s message listener container, a number of other attributes are available. Many of them are passed along to that underlying container. For example, you might want to control the concurrency settings. Following is an example that indicates five core consumers should always be running, but when load increases beyond the capacity of those five, the number of consumers can increase up to 25. At that point, each of those extra consumers can have up to three idle tasks—those where no message is received within the receive timeout of 5 seconds, at which point the consumer will be cleared. The end result of such configuration is that the number of consumers can dynamically fluctuate between 5 and 25 based on the demand for handling incoming messages:

<int-jms:inbound-gateway id="exampleGateway"
    request-destination-name="someQueue"
    request-channel="requestChannel"
    concurrent-consumers="5"
    max-concurrent-consumers="25"
    idle-task-execution-limit="3"/>

This example shows that various settings of the underlying message listener container can be configured directly on the XML element that represents the Spring Integration gateway. The preceding attributes are a small subset of all the configurable properties of the container. When defining your elements in an IDE with good support for XML, such as the SpringSource Tool Suite, you can easily explore the entire set of available attributes.

We’ve now covered the various Spring Integration adapters. You saw how such adapters can be used on both the sending side and the receiving side. You saw the unidirectional channel adapters as well as the bidirectional gateways that enable request-reply messaging. Next, we consider the scenario in which the JMS messaging occurs between two applications that are both using Spring Integration.

9.7. Messaging between multiple Spring Integration runtimes

In the previous sections, you saw the inbound gateway and the outbound gateway. Both play a role in supporting request-reply messaging, but they were discussed separately thus far. That’s because each can be used when you’re limited in the assumptions you can make about the application on the other side. As you might expect, the two gateways can work well together when you have Spring Integration applications on both sides. Figure 9.4 captures this situation by reusing one of the diagrams from the introductory chapter, this time labeled specifically for JMS.

Figure 9.4. A pair of gateways, one outbound and the other inbound. Each is hosted by a separate Spring Integration application. Those two applications share access to a common JMS broker and a pair of destinations.

In the scenario depicted in the figure, it will obviously be necessary to map between the Spring Integration messages used in each application and the JMS messages that are being passed between the applications. We saw several examples of how the adapters use Spring’s JMS MessageConverter strategy to convert to and from JMS messages. So far, the examples have mapped between the JMS message body and the Spring Integration message payload. Likewise, the JMS properties have mapped to and from the Spring Integration message headers. These are by far the most common usage patterns for message mapping with JMS, and they make minimal assumptions about the system on the other side of the message exchange.

In a particular deployment environment, though, it might be well known that Spring Integration–based applications exist on both sides of the JMS destination. One Spring Integration application would act as a producer, and the other would act as a consumer. The interaction may be unidirectional, using the channel adapters we saw earlier in this chapter, or the interaction might involve request-reply exchanges wherein one of the applications contains an outbound gateway and the other contains an inbound gateway (keep in mind that a gateway acts as both a producer and a consumer). If that’s the nature of the deployment model, it may or may not be desirable to pass the entire Spring Integration Message instance as the JMS message body. The default mapping behavior would obviously work in such an environment, but if you want to “tunnel” through JMS instead, for some reason, then you can override the default configuration. To send a Spring Integration Message as the actual body of a JMS message, provide a value of false to the extract-request-payload property of an outbound gateway:

<int-jms:outbound-gateway id="exampleGateway"
    request-destination-name="someQueue"
    request-channel="requestChannel"
    extract-request-payload="false"/>

When passing the Spring Integration Message as the JMS message body, it’s necessary to have a serialization strategy. The standard Java serialization mechanism is one option, because Spring Integration Messages implement the Serializable interface. One thing to keep in mind when choosing that option is that nonserializable values that are stored in the message headers won’t be passed along because they can’t be serialized with that approach. An even more important factor to keep in mind is that Java serialization requires that the same class be defined on both the producer and the consumer sides. Not only must it be the same class, but the version of the class must be the same. JMS facilitates this option by providing support for ObjectMessages, where the body is a Serializable instance.

At first, it seems convenient to pass your domain objects around without any need to think about conversion or serialization, but it’s almost always a bad idea in reality. By requiring exactly the same classes to be available to both the producer and the consumer, this approach violates the primary goal of messaging: loose coupling. Even if you control both sides of the messaging exchange, the fewer assumptions one side makes about the other, the more flexible the application will be. As any experienced developer knows, some of the most regrettable assumptions are those made about the future of an application. For example, if an application needs to evolve to support multiple versions of a certain payload type, reliance on default serialization to and from a single version of a class will be a sure source of regret.

With these twin goals of reducing assumptions and increasing flexibility in mind, let’s consider some other options for serializing data. Probably the most common approach in enterprise integration is to rely on XML representations. Spring Integration provides full support for that option with the object-to-XML marshaller and XML-to-object unmarshaller implementations from the Spring Framework’s oxm module (see chapter 8 for more detail). Another increasingly popular option for serializing and deserializing the payload is to map to and from a portable JSON representation. The advantage of building a solution based on either XML or JSON instead of Java serialization is that the system can be much more flexible. It’s not necessary to have the same version on both sides. In fact, as long as the marshaller and unmarshaller implementations account for it, the producer and consumer sides may even convert to and from completely different object types.

Regardless of the chosen serialization mechanism, you must configure a Message-Converter on the gateway any time you don’t want to rely on the default, which uses Java serialization. You might choose the MarshallingMessageConverter provided by Spring for object-to-XML conversion, or you might implement MessageConverter yourself. Either way, define the MessageConverter within the same Application-Context, and then provide the reference on the outbound gateway or channel adapter’s configuration:

<int-jms:outbound-gateway id="exampleGateway"
    request-destination-name="someQueue"
    request-channel="requestChannel"
    extract-request-payload="false"
    message-converter="customConverter"/>

<bean id="customConverter" class="example.CustomMessageConverter"/>

Generally, we recommend avoiding the tunneling approach because having the mapping behavior on both sides promotes loose coupling. Even then, it’s worth considering the serialization strategy. If a Spring Integration payload is a simple string or byte array, then it’ll map to a JMS TextMessage or BytesMessage respectively when relying on the default MessageConverter implementation. The default conversion strategy also provides symmetric behavior when mapping from JMS. A TextMessage maps to a string payload, and a BytesMessage maps to a byte array payload. But if your Spring Integration payload or JMS body is a domain object, then it’s definitely important to consider the degree of coupling because the default MessageConverter will rely on Java serialization at that point.

Spring Integration provides bidirectional XML transformers in its XML module (again, see chapter 8 for more details), and it provides bidirectional JSON transformers in the core module. Both the XML and JSON transformers can be configured using simple namespace-defined elements in XML. You can provide the object-to-XML or object-to-JSON transformer upstream from an outbound JMS channel adapter or gateway, and you can provide the XML-to-object or JSON-to-object transformer downstream from an inbound JMS channel adapter or gateway. One advantage of relying on the transformer instances is that you can reuse them in multiple messaging flows. For example, you might also be receiving XML or JSON from an inbound file adapter, and you might be sending XML or JSON to an outbound HTTP gateway.

If such opportunities for reuse aren’t relevant in your particular application, you may prefer to encapsulate the serialization behavior. You can rely on any implementation of Spring’s MessageConverter strategy interface. As mentioned earlier, the MarshallingMessageConverter is available in the Spring Framework. A similar JSON-based implementation was introduced in Spring 3.1, but if you’re using an earlier version, it wouldn’t be difficult to implement in the meantime. You can provide any other custom logic or delegate to any other serialization library that you choose. If going down that path, you would define the chosen MessageConverter implementation as a bean and then reference it from the channel adapter or gateway’s message-converter attribute.

9.8. Managing transactions with JMS channel adapters and gateways

Typically, the requirements of an application that utilizes a messaging system include such terms as “guaranteed delivery” and provide details about retry policies in anticipation of potential failure scenarios. Those requirements usually support one key concern: messages can’t be lost even if errors occur while processing them. In the worst case, after a number of attempts, a message should be delivered to a dead letter queue. Proper transaction management is essential when confronting such requirements. The primary goal when managing transactions in general is to ensure that data is always accounted for. No party should relinquish responsibility until another party has assumed responsibility. In a messaging interaction, the two parties that may assume responsibility at any given time are the producer and the consumer.

Therefore, when working with JMS, it’s important to understand exactly how and where to apply transactions on the producer side as well as on the consumer side. It’s also important to consider whether the unit of work that should be transactional includes not only interaction with the JMS message broker but also some other transactional resource, such as a database. Finally, it’s important to understand the advanced options (those that may be outside of the JMS specification) that are available with a given broker, such as the retry policies. Such options are beyond the scope of this book, but the JMS provider’s documentation is a good place to start. If you’re using ActiveMQ, its options are covered in detail in ActiveMQ in Action. Here, we cover the general aspects of JMS transactions and specifically how they can be configured on Spring Integration’s JMS channel adapters and gateways.

9.8.1. JMS transaction basics

Let’s revisit the creation of a JMS Session, the object that represents a unit of work in the JMS API. As you saw at the beginning of this chapter, the Session acts as a factory for creating the messages as well as the producer and consumer instances. When creating a Session from a Connection, you must decide whether that Session should be transactional. Here’s a simple example using the JMS API directly (without Spring):

Connection connection = connectionFactory.createConnection();
int autoAck = Session.AUTO_ACKNOWLEDGE;
Session session = connection.createSession(true, autoAck);

That boolean value (true in this example) indicates that the Session should be transactional. Note that the second argument indicates the acknowledge mode for the Session. The two values are mutually exclusive. If you provide a value true to indicate that you want a transactional Session, the acknowledge mode value will be ignored. On the other hand, if you provide false to indicate that you don’t want a transactional Session, then the value will play a role. We briefly return to the acknowledge modes in a moment, but first let’s discuss the publishing side where things are straightforward.

When publishing JMS messages, the transactional boolean value passed when creating the Session plays an important role. The Session is used as a factory for creating any MessageProducer instances, and the transactional characteristics of those producers would reflect that boolean flag. If the Session isn’t transactional, then publishing a message will truly be a fire-and-forget operation, and there will be no way to undo the publish operation or to group multiple operations into a single unit of work. In contrast, when the transactional setting is enabled on a Session, any messages published with that Session are only made available within the broker upon committing the transaction. That means that if something goes wrong, any messages published with that Session in the scope of that transaction can be rolled back instead. The commit() and rollback() methods are available directly on the Session.

When using a transactional Session, similar behavior is available on the receiving side. A MessageConsumer created from a transactional Session won’t permanently take its messages from the broker until the Session’s transaction is committed. If something goes wrong after receiving a message, calling rollback() on the Session will undo the reception. That means any messages received within the scope of the rolled-back transaction may be eligible for redelivery, potentially to a different consumer.

As mentioned previously, when the transactional boolean value is false, the acknowledge mode plays a role in the message reception. The valid integer values for acknowledge modes are defined as constants on the Session class. When using any Spring or Spring Integration JMS support, AUTO_ACKNOWLEDGE is the default. You can set the value to CLIENT_ACKNOWLEDGE instead, which indicates that you expect the client consuming the JMS messages to explicitly invoke the acknowledge() method on those messages. An acknowledge() call on a single Message instance will acknowledge all previously unacknowledged Messages that have been consumed on that same Session. A third option, called DUPS_OK_ACKNOWLEDGE, is similar to AUTO_ACKNOWLEDGE in that no explicit calls are required of the consuming client. It differs in that the acknowledgments may be sent lazily, allowing for the chance that duplicate messages might be received in the case of certain failures within the window of vulnerability. The details of acknowledge modes are covered in the JMS specification. You can also find some information in Spring’s Javadoc and XML schema documentation.

After our detailed discussion earlier in this chapter, you know that the Spring Framework’s JMS support includes a component that serves as a MessageListener container. Session management for message consumption is one of the responsibilities of such a container. In consideration of the mutual exclusivity between the boolean value to indicate a transactional Session and the acknowledge mode to be used otherwise, the schema-based configuration of the listener container rolls these options into a single acknowledge property. It can take a value that corresponds to any of the three acknowledge modes defined by the JMS specification, or it can be set to transacted. Here are two examples to illustrate:

<!-- Transactional MessageListener Container -->
<jms:listener-container acknowledge="transacted">
    <jms:listener destination="someQueue" ref="someListener"/>
</jms:listener-container>

<!-- Non-transactional auto-ack MessageListener Container -->
<jms:listener-container acknowledge="auto">
    <jms:listener destination="someQueue" ref="someListener"/>
</jms:listener-container>

When configuring Spring Integration’s inbound channel adapter or gateway for JMS, you’ll see the same attribute. It accepts any value from the same enumeration: auto, client, dups-ok, or transacted. Here are a couple examples:

<int-jms:message-driven-channel-adapter id="exampleChannelAdapter"
                                        channel="jmsMessages"
                                        destination-name="queue1"
                                        acknowledge="transacted"/>

<int-jms:inbound-gateway id="exampleGateway"
                         request-destination-name="queue2"
                         request-channel="jmsMessages"
                         acknowledge="dups-ok"/>

One important final note: if no value is provided, the default will be auto acknowledge mode, and hence Sessions won’t be transactional. Transactions add overhead, so if you’re sending nonessential event data where occasional message loss isn’t a problem, the default might be fine. But if your messages are carrying document data to be processed, be sure to consider this setting carefully.

9.8.2. A note about distributed transactions

The Spring Framework provides an abstraction for transaction management that makes it easy to switch between different strategies for handling transactions within an application. In most cases, only configuration changes are necessary. Most likely, that change is limited to replacing a single bean because the code depends on the abstraction. Furthermore, because transaction support is typically handled by interception within a proxy, end user code is almost never affected directly.

The key interface in Spring that provides this abstraction is the Platform-TransactionManager. It defines the methods you’d expect for managing transactions regardless of the details of the underlying system:

public interface PlatformTransactionManager {

  TransactionStatus getTransaction(TransactionDefinition definition)
          throws TransactionException;

  void commit(TransactionStatus status) throws TransactionException;

  void rollback(TransactionStatus status) throws TransactionException;

}

As far as transaction management strategies are concerned, broadly speaking, there are two main choices: local or global transaction management. What we mean by local is that transactions can be managed directly against the transactional resource without any need for a mediator. In the case of JMS, the local transactional resource would be the JMS Session instance. As you saw earlier, a Session can be created with a simple boolean flag to indicate whether it should be transactional. The JMS Session class also defines commit and rollback methods.

Another example that might clarify this terminology is the JDBC Connection class. It’s also a local transactional resource because it provides commit and rollback methods. Whereas the JMS Session is transactional upon creation (assuming the flag is true), the JDBC Connection has a setAutoCommit() method such that passing a value of false will disable autocommit mode. Explicit commit (or rollback) is then required, so the Connection is then transactional in that multiple operations can be handled within the scope of a single unit of work. A JMS Session and a JDBC Connection are both good examples of resources that enable local transaction management. The way the respective APIs expose the functionality is slightly different, but that’s where Spring’s PlatformTransactionManager can step in to provide a consistent view. In fact, implementations are available out of the box for these two cases: JmsTransactionManager and DataSourceTransactionManager.

Earlier we mentioned a common case where a unit of work spans a JMS message exchange and one or more database operations. In those cases, it might be tempting to move toward a global transaction solution. You could rely on XA-capable resources and a Java Transaction API (JTA) implementation that supports two-phase commit as the transaction manager, but it’s not always necessary to go that far. A distributed transaction in the more general sense means more than one resource is involved. Rather than assuming you need to use XA and two-phase commit along with the extra overhead they bring, you should consider all of the options.

It might be possible to find some middle ground such that you can maintain the simplicity of the local transaction management but chain multiple local resources together. The trade-off is a requirement to carefully consider the order of operations and how to manage certain rare failure scenarios. One of the most common patterns when dealing with messaging is to have a process that begins with a received JMS message but then invokes a database operation based on that message’s content. For example, imagine an order being received and then inserted into the database. The JMS Session is one local transactional resource, and the JDBC Connection is another. If a failure occurs while processing the message content or inserting the database record, then an exception could trigger a rollback of both the JDBC and JMS transactions. The JMS message would be eligible for redelivery, and the database would remain unchanged. If everything goes smoothly, the database insert is committed, and the JMS message is permanently removed from its destination.

The rare situation that could lead to a problematic result is if the database transaction commits, but for some reason, such as a system-level failure, the JMS transaction rolls back afterward. In that case, the JMS message could potentially be redelivered, and so the application must be able to handle such duplicates. If the database operation is idempotent, then there might not be any problem at all. In an idempotent operation, a record would be ignored if the same data already exists, likely based on a where clause condition. If the operation isn’t inherently idempotent, there might be more work involved to add similar logic at a higher level of the application. Perhaps any message where the JMSRedelivered property is true can be checked against the database prior to invoking the normal insert operation. If it’s recognized as a duplicate that has already been handled successfully, the message could be ignored, and its JMS transaction could be committed so it won’t be redelivered anymore. Keep in mind that while that sounds like added overhead, such redeliveries would likely be extremely rare. Depending on the particular application structure, these types of solutions might be simpler and add less overhead than a full XA two-phase commit option. For much more detail on these types of distributed transaction patterns and the trade-offs involved, refer to “Distributed Transactions in Spring, with and without XA,” by Dr. David Syer (available at http://mng.bz/9DF4). David is the lead of the Spring Batch project and a committer on several other Spring projects, including Spring Integration.

9.9. Summary

We covered a lot of ground in this chapter. Considering the central role that JMS plays in many enterprise Java applications, we wanted to make sure it was clear where Spring Integration overlaps with JMS and where the two can complement each other. You saw the relationship between the two message structures and how to map between them. You also learned how the underlying Spring Framework provides base functionality that greatly simplifies the use of JMS and how Spring Integration takes that even further with its declarative configuration and higher level of abstraction. After walking through both the unidirectional channel adapters and the request-reply gateways, we dove into a bit of depth regarding transactions. At this point, you should be well prepared to build Spring Integration applications where none of the code is directly tied to the JMS API while still benefiting from the full power of whatever underlying JMS provider you choose. In chapter 10, we cover Spring Integration’s support for yet another type of messaging system, one that you most likely use on a daily basis: email.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.138.104