Now that you have an idea of the high-level concepts of messaging and Spring Inte-gration, it’s time to dive in deeper. As mentioned in chapter 1, the three fundamen-tal concepts in Spring Integration are messages, channels, and endpoints.
Endpoints are discussed in a chapter of their own (chapter 4), but first you get to read about messages and channels.
In our introduction to messages, we show how the information exchanged by Spring Integration is organized. When describing channels, we discuss the conduits through which messages travel and how the different components are connected.
Spring Integration provides a variety of options when it comes to channels, so it’s important to know how to choose the one that’s right for the job. Finally, there are a couple of more advanced components you can use to enhance the functionality of channels: dispatchers and interceptors, which we discuss at the end of the chapter.
This chapter covers
Introducing messages and channels
How different types of channels work
Customizing channel functionality with dispatchers and interceptors
46 CHAPTER 3 Messages and channels
Throughout the remaining chapters of this book, many include a section called
“Under the hood,” where we discuss the reasoning behind the API and provide details about the internal workings of the concepts discussed. But in this chapter we focus on the API itself, because messages and channels are fundamental concepts. Understand-ing how SprUnderstand-ing Integration represents them is an essential foundation for buildUnderstand-ing the rest of your knowledge about the framework. The best way to get to trust some-thing is to understand how it works.
Now let’s go inside and look around!
3.1 Introducing Spring Integration messages
As you saw in chapter 2, message-based integration is one of the major enterprise inte-gration styles. Spring Inteinte-gration is based on it because it’s the most flexible and allows the loosest coupling between components.
We start our journey through Spring Integration with one of its building blocks:
the message. In this section, we discuss the message as a fundamental enterprise inte-gration pattern and provide an in-depth look at how it’s implemented in Spring Inte-gration.
3.1.1 What’s in a message?
A message is a discrete piece of information sent from one component of the software system to another. By piece of information, we mean data that can be represented in the system (objects, byte arrays, strings, and so forth) and passed along as part of a sage. Discrete means that messages are typically independent units. Each discrete mes-sage contains all the information that’s needed by the receiving component for performing a particular operation.
Besides the application data, which is destined to be consumed by the recipient, a message usually packs meta-information. The meta-information is of no interest to the application per se, but it’s critical for allowing the infrastructure to handle the infor-mation correctly. We call this meta-inforinfor-mation message headers.
The obvious analogy here is a letter. A letter contains an individual message des-tined to the recipient but is also wrapped in an envelope containing information that’s useful to the mailing system but not to the recipient: a delivery address, the stamp indicating that mailing fees are paid, and various stamps applied by the mailing office for routing the message more efficiently.
Based on the role they fulfill, we distinguish three types of messages:
Document messages that contain only information
Command messages that instruct the recipient to perform various operations
Event messages that indicate notable occurrences in the system and to which the recipient may react
Now that we have an overview of the message concept, we can talk about Spring Inte-gration’s approach to it. Next we look at it from an API and implementation stand-point and then see how to create a message. Usually, you won’t need to create the
47 Introducing Spring Integration messages
message yourself, but to understand the Spring Integration design, it’s important to see how message creation works.
3.1.2 How it’s done in Spring Integration
The nature of a message in Spring Integration is best expressed by the Message interface:
package org.springframework.integration;
public interface Message<T> { MessageHeaders getHeaders();
T getPayload();
}
Let’s examine this code in detail. A message is a generic wrapper around data that’s transported between the different components of the system. The wrapper allows the framework to ignore the type of the content and to treat all messages in the same way when dealing with dispatching and routing.
Having an interface as the top-level abstraction enables the framework to be exten-sible by allowing extensions to create their own message implementations in addition to those included with the framework. It’s important to remember that, as a user, cre-ating your own message implementations is hardly necessary because you usually won’t need to deal with the message objects directly.
The content wrapped by the message can be any Java Object, and because the interface is parameterized, you can retrieve the payload in a type-safe fashion.
Besides the payload, a message contains a number of headers, which are metadata associated with that message. For example, every message has an identifier header that uniquely identifies the message instance. In other cases, message headers may contain correlation information indicating that various individual messages belong to the same group. A message’s headers may even contain information about the origin of the message. For example, if the message was created from data coming from an external source (like a JMS [Java Message Service] queue or the filesystem), the mes-sage may contain information specific to that source (like the JMS properties or the path to the file in the filesystem).
The MessageHeaders are a Map with some additional behavior. Their role is to store the header values, which can be any Java Object, and each value is referenced by a header name:
package org.springframework.integration;
public final class MessageHeaders
implements Map<String, Object>, Serializable { /* implementation omitted */
}
48 CHAPTER 3 Messages and channels
WHERE ARE THE SETTERS?
As you may have noticed from the definition of the Message interface, there is no way to set something on a Message. Even the MessageHeaders are an immutable Map: try-ing to set a header on a given Message instance will yield an UnsupportedOperation-Exception. This happens for a good reason: messages aren’t modifiable. You might worry about how these messages are created and why they can’t be modified. The short answer is that you don’t need to create them yourself and that they’re immuta-ble to avoid issues with concurrency.
In publish-subscribe scenarios, the same instance of a message may be shared among different consumers. Immutability is a simple and effective way to ensure thread safety. Imagine what would happen if multiple concurrent consumers were to modify the same Message instance simultaneously by changing its payload or one of its header values.
As mentioned, Spring Integration provides a few implementations of its own for the Message interface, mostly for internal use. Instead of using those implementations directly or creating implementations of your own, you can use Spring Integration’s MessageBuilder when you need to create a new Message instance.
The MessageBuilder creates messages from either a given payload or another mes-sage (a facility necessary for creating mesmes-sage copies). There are three steps in this process, shown in figure 3.1.
The figure shows the builder creating a message from the payload, but the same steps apply for creating a message from another message. Steps 1 and 3 are mandatory in that they have to take place whenever a new message is created using the Message-Builder, whereas step 2 is optional and needs to be performed only when custom headers are required. It should be noted that the MessageBuilder initializes a num-ber of headers that are needed by default by the framework, and this is one of the sig-nificant advantages of using the MessageBuilder over instantiating messages directly:
you don’t have to worry about setting up those headers yourself.
As an example, creating a new message with a String payload can take place as follows:
Message<String> helloMessage =
MessageBuilder.withPayload("Hello, world!").build();
Builder Payload
Builder
Headers
Builder
Message
Step 1: A Builder is created with Payload
Step 2: Headers are added
Step 3: A Message is created by the Builder
Figure 3.1 Creating a message with the MessageBuilder
49 Introducing Spring Integration channels
The MessageBuilder also provides a variety of methods for setting the headers, including options for setting a header value only if that header hasn’t already been set. A more elaborate variant of creating the helloMessage could be something like this:
Message<String> helloMessage =
MessageBuilder.withPayload("Hello, world!") .setHeader("custom.header", "Value")
.setHeaderIfAbsent("custom.header2", "Value2") .build();
You can also create messages by copying the payload and headers of other messages and can change the headers of the resulting instance as necessary:
Message<String> anotherHelloMessage = MessageBuilder.fromMessage(helloMessage)
.setHeader("custom.header", "ChangedValue")
.setHeaderIfAbsent("custom.header2", "IgnoredValue") .build();
Now you know what messages are in Spring Integration and how you can create them.
Next, we discuss how messages are sent and received through channels.
3.2 Introducing Spring Integration channels
Messages don’t achieve anything by sitting there all by themselves. To do something useful with the information they’re packaging, they need to travel from one compo-nent to another, and for this they need channels, which are well-defined conduits for transporting messages across the system.
Let’s go back to the letter analogy. The sender creates the letter and hands it off to the mailing system by depositing it in a well-known location: 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 a reply. The sender is unaware of who routes the message or, sometimes, even who may be the physical reader of the letter (think about writing to a government agency). From a logical standpoint, the channel is much like a mailbox: a place where components (producers) deposit messages that are later processed by other components (consum-ers). 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 from other enter-prise integration frameworks, is its emphasis on the role of channels in defining the enterprise integration strategy. Channels aren’t just information transfer components;
they play an active role in defining the overall application behavior. The business pro-cessing takes place in the endpoints, but you can alter the channel configuration to completely change the runtime characteristics of the application.
We explain channels from a logical perspective and offer overviews of the various channel implementations provided by the framework: what’s characteristic to each of
50 CHAPTER 3 Messages and channels
them, and how you can get the most from your application by using the right kind of channel for the job.
3.2.1 Using channels to move messages
To connect the producers and consumers configured in an application, you use a channel. All channels in Spring Integration implement the following MessageChannel interface, which defines standard methods for sending messages. Note that it provides no methods for receiving messages:
package org.springframework.integration;
public interface MessageChannel { boolean send(Message<?> message);
boolean send(Message<?> message, long timeout);
}
The reason no methods are provided for receiving messages is because Spring Inte-gration differentiates clearly between two mechanisms through which messages are handed over to the next endpoint—polling and subscription—and provides two dis-tinct types of channels accordingly.
3.2.2 I’ll let you know when I’ve got something!
Channels that implement the SubscribableChannel interface, shown below, take responsibility for notifying subscribers when a message is available:
package org.springframework.integration.core;
public interface SubscribableChannel extends MessageChannel { boolean subscribe(MessageHandler handler);
boolean unsubscribe(MessageHandler handler);
}
3.2.3 Do you have any messages for me?
The alternative is the PollableChannel, whose definition follows. This type of chan-nel requires the receiver or the framework acting on behalf of the receiver to periodi-cally check whether messages are available on the channel. This approach has the advantage that the consumer can choose when to process messages. The approach can also have its downsides, requiring a trade-off between longer poll periods, which may introduce latency in receiving a message, and computation overhead from more frequent polls that find no messages:
package org.springframework.integration.core;
public interface PollableChannel extends MessageChannel { Message<?> receive();
51 Introducing Spring Integration channels
Message<?> receive(long timeout);
}
It’s important to understand the characteristics of each message delivery strategy because the decision to use one over the other affects the timeliness and scalability of the system. From a logical point of view, the responsibility of connecting a consumer to a channel belongs to the framework, thus alleviating the complications of defining the appropriate consumer types. To put it plainly, your job is to configure the appro-priate channel type, and the framework will select the approappro-priate consumer type (polling or event-driven).
Also, subscription versus polling is the most important criterion for classifying channels, but it’s not the only one. In choosing the right channels for your applica-tion, you must consider a number of other criteria, which we discuss next.
3.2.4 The right channel for the job
Spring Integration offers a number of channel implementations, and because MessageChannel is an interface, you’re also free to provide your own implementations.
The type of channel you select has significant implications for your application, includ-ing transactional boundaries, latency, and overall throughput. This section walks you through the factors to consider and through a practical scenario for selecting appro-priate channels. In the configuration, we use the namespace, and we 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 for many businesses is the need to get paid, so making sure you can charge the provided credit card is a high priority. You also want to ensure that, as seats are booked, an update occurs to indicate one less seat is available on the flight so you don’t overbook the flight. The system must also send a confirmation email with details of the booking and additional information on the check-in process.
In addition to a website, the internet booking application exposes a REST interface to allow third-party integration for flight comparison sites and resellers. Because most of the airline’s premium customers come through the airline’s website, any design should allow you to prioritize bookings originating from its website over third-party integration requests to ensure that the airline’s direct customers experience a respon-sive website even during high load.
The selection of channels is based on both functional and nonfunctional require-ments, and several factors can help you make the right choice. Table 3.1 provides a brief overview of the technical criteria and the best practices you should consider when selecting the most appropriate channels.
Let’s see how these criteria apply to our flight-booking sample.
52 CHAPTER 3 Messages and channels
Table 3.1 How do you decide what channel to use?
Decision factor What factors must you consider?
Sharing context
– Do you need to propagate context information between the successive steps of a process?
– Thread-local variables are used to propagate context when needed in several places where passing via the stack would needlessly increase coupling, such as in the transaction context.
– Relying on the thread context is a subtle form of coupling and has an impact when considering the adoption of a highly asynchronous staged event-driven architecture (SEDA) model. It may prevent splitting the processing into concurrently executing steps, prevent partial failures, or introduce security risks such as leaking permis-sions to the processing of different messages.
Atomic boundaries
– Do you have all-or-nothing scenarios?
– Classic example: bank transaction where credit and debit should either both suc-ceed or both fail.
– Typically used to decide transaction boundaries, which makes it a specific case of context sharing. Influences the threading model and therefore limits the available options when choosing a channel type.
Buffering messages
– Do you need to consider variable load? What is immediate and what can wait?
– The ability of systems to withstand high loads is an important performance factor, but load is typically fluctuating, so adopting a thread-per-message-processing sce-nario requires more hardware resources for accommodating peak load situations.
Those resources are unused when the load decreases, so this approach could be expensive and inefficient. Moreover, some of the steps may be slow, so resources may be blocked for long durations.
– Consider what requires an immediate response and what can be delayed; then use a buffer to store incoming requests at peak rate, and allow the system to process them at its own pace. Consider mixing the types of processing—for example, an online purchase system that immediately acknowledges receipt of the request, per-forms some mandatory steps (credit card processing, order number generation), and responds to the client but does the actual handling of the request (assembling the items, shipping, and so on) asynchronously in the background.
Blocking and nonblocking operations
– How many messages can you buffer? What should you do when you can’t cope with demand?
– If your application can’t cope with the number of messages being received and no limits are in place, you may exhaust your capacity for storing the message backlog or breach quality-of-service guarantees in terms of response turnaround.
– Recognizing that the system can’t cope with demands is usually a better option than continuing to build up a backlog. A common approach is to apply a degree of self-limiting behavior to the system by blocking the acceptance of new messages where the system is approaching its maximum capacity. This limit commonly is a maximum number of messages awaiting processing or a measure of requests received per second.
– Where the requester has a finite number of threads for issuing requests, blocking those threads for long periods of time may result in timeouts or quality-of-service breaches. It may be preferable to accept the message and then discard it later if system capacity is being exceeded or to set a timeout on the blocking operation to avoid indefinite blocking of the requester.
53 Introducing Spring Integration channels
3.2.5 A channel selection example
Using the default channel throughout, we have three channels—one accepting requests and the other two connecting the services:
<channel id="bookingConfirmationRequests"/>
In Spring Integration, the default channels are SubscribableChannels, and the mes-sage transmission is synchronous. The effect is simple: one thread is responsible for invoking the three services sequentially, as shown in figure 3.2.
Because all operations are executing in a single thread, a single transaction encompasses those invocations. That assumes that the transaction configuration doesn’t require new transactions to be created for any of the services.
Consumption model
– How many components are interested in receiving a particular message?
– There are two major messaging paradigms: point-to-point and publish-subscribe. In
– There are two major messaging paradigms: point-to-point and publish-subscribe. In