This article is based on SpringIntegration in Action, to be published August-2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code ‘java40beat’ and get 40% discount on eBooks and pBooks ]
also read:
Introduction
Messages don’t achieve anything sitting all by themselves. In order to do something useful and pass along the information they’re packaging, they need to travel from one component to another and, for this, we need channels, which are well-defined conduits for transporting messages across the system.
To use a letter analogy, the sender creates a letter and hands it off to the mailing system by depositing it into a well-known location, which is the mailbox. From there on, the letter is completely under the control of the mailing system, which delivers it to various waypoints, until it reaches the recipient. The most that the sender can expect is another reply—but, otherwise, who routes the message and who the physical recipient of the letter is (if, for example, the letter is addressed to an organization rather than a person) are things that are not known to it. From a logical standpoint, the channel is very much like a mailbox—a place where certain components, called Producers, deposit messages that are later on processed by other components, called Consumers. This way, Producers and Consumers are decoupled from each other and are only concerned about what kinds of messages they can send and receive, respectively.
One distinctive trait of Spring Integration, which differentiates it among other enterprise integration
frameworks, is the emphasis that the channels play in defining the enterprise integration strategy. They’re not just plain information transfer components, but they play an active role in defining the overall application behavior. The business processing takes place in the endpoints, but you just have to alter the channel configuration to completely change the application’s runtime characteristics.
So, you’ll see channels presented from a logical perspective, but you’ll also get an overview of the various
channel implementations provided by the framework, their individual characteristics, and how you can get the most of your application by using the right kind of channel for the job.
Using channels to move messages
To connect the producers and consumers configured within the application we use a channel. All channels within Spring Integration implement the MessageChannel interface shown below, which defines standard methods for the sending of messages. Note, however, that it provides no methods to allow the receipt of messages.
public interface MessageChannel { String getName(); boolean send(Message<?> message); boolean send(Message<?> message, long timeout); }
The reason for this is that the message channels provided by Spring Integration fall into two distinct categories that primarily differ with regard to the mechanism for handing the message over to the next endpoint.
Do you have any message for me?
PollableChannels as defined by the interface shown below require the receiver or the framework acting on behalf of the receiver to carry out a periodic check to see if messages are available on the channel. This approach has the advantage that the consumer can process messages at a time of their choosing. The approach can also have downsides, creating a tradeoff between longer poll periods, which create latency in the processing of a message, and computation overhead from more frequent polls that find no messages.
public interface PollableChannel extends message channel { Message<?> receive(); Message<?> receive(long timeout); List<Message<?>> clear(); List<Message<?>> purge(message selector selector); }
I’ll let you know when I’ve got something*
The alternative offered by Spring Integration is the SubscribableChannel interface, which is implemented by channels that take responsibility for notifying subscribers when a message is available. In most cases, however, the JOJO consumer code will remain unaware of the capabilities of the channel from which messages are received since the framework will take responsibility for subscribing or polling, as appropriate.
public interface SubscribableChannel extends message channel { boolean subscribe(message handler handler); boolean unsubscribe(message handler handler); }
While it is important to understand the implications of whether a channel pushes out messages or is periodically polled, in most cases the framework will take on the concern of connecting a consumer to a channel, alleviating the complications of defining the appropriate consumer types. To put it plainly, your job is to select the appropriate channel type, and the framework will select the appropriate consumer type (polling or event driven).
The right channel for the job
Spring Integration offers a number of different channel implementations and since is an interface you are also free MessageChannel to provide your own implementations. The type of channel selected will have significant implications for your application, including transactional boundaries, latency, and overall throughput. This section will walk you through the factors to consider and a practical scenario of selecting appropriate channels. In the configuration, we will use the namespace but we will also discuss which concrete channel implementation will be instantiated by the framework.
In our flight-booking Internet application, a booking confirmation results in a number of actions. Foremost in the mind of many businesses is the need to get paid, so making sure that we can charge the provided credit card is a high priority. We will also want to update the number of available seats to ensure we don’t overbook the flight. The system will also be required to send a confirmation email with details of the booking and additional information on the check in process. In addition to a website, our Internet booking application exposes a REST interface to allow third-party integration for flight comparison sites and resellers. Since most of the airline’s premium customers come through the airline’s own website, any design should allow us to prioritise bookings originating from there over third-party integration requests in order to ensure that the airline’s direct customers experience a responsive website even in times of high load.
Is choosing a channel all that difficult?
The selection of channels will be based on both functional and nonfunctional requirements, and there are several decision factors that can help us making the right choice. This section provides a brief overview of the technical criteria and the best practices that you should consider when selecting the correct channels.
Table 1 Deciding which channel to use
Let’s see how these criteria apply to our flight booking sample.
A channel selection example
Using the default channel throughout, we have three channels: one accepting requests and the other two connecting our services.
<channel id="bookingConfirmationRequests"/> <service-activator input-channel="bookingConfirmationRequests" output- channel="chargedBookings" <channel id="chargedBookings" /> <service-activator input-channel="chargedBookings" output-channel="emailConfirmationRequests" <channel id="emailConfirmationRequests" /> <outbound-channel-adapter channel="emailConfirmationRequests" ref="emailConfirmationService/>
In Spring Integration, the default channels are SubscribableChannels, and the message transmission is synchronous. The effect of this is simple—we have one thread responsible for invoking the three services sequentially, as shown in figure 1.
Figure 1 Diagram of the threading model of service invocation in the airline applicationBecause all operations are executing in a single thread, there is a single transaction encompassing those invocations. If the transaction configuration does not require new transactions to be created for any of the services, the three service invocations will occur within the scope of transaction.
Figure 2 shows what you get when you configure an application using the default channels, which are subscribable and synchronous.
Figure 2 Diagram of the threading model of service invocation when using a default channelHaving all service invocations happening in one thread and encompassed by a single transaction is a mixed blessing: it could be a good thing in certain applications where all three operations must be executed atomically but takes its toll on the scalability and robustness of the application.
But email is slow and our servers are unreliable
The basic configuration is all well and good in the sunny day case when the email server is always up and responsive and the network is 100 percent reliable. Reality is different. Our application needs to work in a real world where the email server is sometimes overloaded and the network sometimes fails. Analysing our actions in terms of what we need to do now and what we can afford to do later is a good way of deciding on what service calls we should block. Billing the credit card and updating the seat availability are clearly things we need to do now in order to be able to respond with confidence that the booking has been made. Sending the confirmation email is not time critical and we don’t want to refuse bookings simply because the mail server is down. Therefore, introducing a queue between the mainline business logic execution and the confirmation email service will allow us to do just that—charge the card, update availability, and send the email confirmation when we can.
Introducing a queue on our emailConfirmationRequests channel will allow the thread passing in the initial
message to return as soon as the credit card has been charged and the seat availability has been updated. Changing the Spring Integration configuration to do this is as simple as adding a child element to the .
<channel id="bookingConfirmationRequests"/> <service-activator input-channel="bookingConfirmationRequests" output- channel="chargedBookings" <channel id="chargedBookings" /> <service-activator input-channel="chargedBookings" output-channel="emailConfirmationRequests" <channel id="emailConfirmationRequests"> <queue /> </channel>
Let’s recap how the threading model changes by introducing the QueueChannel, as shown in figure 3.
Figure 3 Diagram of the threading model of service invocation when using a QueueChannelBecause now there isn’t a single thread context that encompasses all invocations, the transaction boundaries change as well. Essentially, every operation that is executing on a separate thread executes in a separate transaction, as shown in figure 4.
Figure 4 Diagram of transactional boundaries when a QueueChannel is usedWe have replaced one of the default channels with a buffered QueueChannel and have set up an asynchronous communication model. What we got was some confidence that long-running operations will not block the application because some component is down or just takes a long time to respond. But, now we have another challenge: what if we need to connect one producer with not only one but two (or more) consumers?
Telling everyone who needs to know that a booking occurred
Up until this point, we have been looking at scenarios where a number of services are invoked in sequence with the output of one service becoming the input of the next service in the sequence. This works well when the result of a service invocation only needs to be consumed once; however, it is common that more than one consumer may be interested in receiving certain messages. In our current version of the channel configuration, bookings that have been successfully billed and have been recorded by the seat availability service pass directly into a queue for email confirmation. In reality, this information would be of interest to a number of services within our application and systems within the enterprise such as Customer Relationship Management systems, which track customer purchases to better target promotions, as well as finance systems monitoring the financial health of the enterprise as a whole.
In order to allow the delivery of the same message to more than one consumer, we will introduce a publish/subscribe channel after the availability check. The publish/subscribe channel provides one-to-many semantics rather than one-to-one semantics provided by most channel implementations. This can be particularly useful when we want the flexibility to easily add consumers to the configuration. If the name of the publish/subscribe channel is known, that is all that is required for the configuration of additional consumers, with no changes to the core application configuration.
The publish/subscribe channel does not in itself support queuing, although it does support asynchronous
operation. This can be done by providing a task executor, which is then used to deliver messages to each of the subscribers in separate threads. This may, however, still block the main thread sending the message on the channel where the task executor is configured to use the caller thread or block the caller thread where the underlying thread pool is exhausted.
In order to ensure that a backlog in sending email confirmations does not block either the sender thread or the entire thread pool for the task executor, we can connect the new pub/sub channel to the existing email confirmation queue by means of a bridge. The bridge is an EIP pattern that supports the connection of two channels. So, this will allow the pub/sub channel to deliver to the queue and then have the thread return immediately.
<channel id="bookingConfirmationRequests"/> <service-activator input-channel="bookingConfirmationRequests" output- channel="chargedBookings" <channel id="chargedBookings" /> <service-activator input-channel="chargedBookings" output-channel="emailConfirmationRequests" <publish-subscribe-channel id="completedBookings" /> <bridge input-channel="completedBookings" output-channel="emailConfirmationRequests" /> <channel id="emailConfirmationRequests"> <queue /> </channel> <outbound-channel-adapter channel="emailConfirmationRequests" ref="emailConfirmationService"
Now that we have made it possible to connect one producer with multiple consumers by the means of a publish/subscribe channel, let’s get to the last challenge and emerge victoriously with our drastically improved application: what if “first come, first served” isn’t always right?
Some customers are more equal than others
In order to ensure that the airline’s direct customers have the best possible user experience, we said we wanted to prioritise the processing of their requests to allow us to render the response as quickly as possible. Using a comparator that prioritises direct customers over indirect, we can modify the first channel to be a priority queue. This will cause the framework to instantiate an instance of PriorityChannel, which creates a queue that is able to prioritise the order in which the messages are received. In this case, we are providing an instance of class implementing Comparator<Message>.
<channel id="bookingConfirmationRequests"> <priority-queue comparator="customerPriorityComparator" /> </channel> <service-activator input-channel="bookingConfirmationRequests" output- channel="chargedBookings" <channel id="chargedBookings" /> <service-activator input-channel="chargedBookings" output-channel="emailConfirmationRequests" <publish-subscribe-channel id="completedBookings" /> <bridge input-channel="completedBookings" output-channel="emailConfirmationRequests" /> <channel id="emailConfirmationRequests"> <queue /> </channel> <outbound-channel-adapter channel="emailConfirmationRequests" ref="emailConfirmationService"
also read:
Summary
What you just saw is an example of applying different types of channels for solving the different requirements with which the application has challenged us. We started with the defaults and, as we worked through the example, we replaced several channel definitions with the ones that are most suitable for each particular situation encountered. What’s most important is that every type of channel has its own justification, and what may be recommendable in one particular use case may not be something you want to use somewhere else. We illustrated the decision process with the criteria that we find the most relevant in every case.