Getting down to business
91Domain-driven transformation
5.5 Chaining endpoints
In many cases, messages flow linearly through the system from endpoint to endpoint, separated by direct chan- nels. In these cases, configuration can be unnecessarily verbose if users are required to create channels between each of the endpoints explicitly. Spring Integration allows you to create those channels implicitly using a <chain> element (figure 5.5).
M inputChannel
outputChannel Implicit channels
Endpoint chain
Figure 5.5 Endpoints can be chained together with channels implicitly created using a chain element.
101
Chaining endpoints
As discussed earlier, synchronous channels are used by default to preserve transac- tional boundaries. In a chain, the synchronous channels needn’t be specified, but there’s no way to override the type of channel used. The argument to do things this way is to improve the readability of the configuration files by reducing repetition but not sacrificing the option of a more explicit configuration where needed.
You’ve seen several examples in this chapter of a single component performing one focused, well-defined task. This is a good thing from the perspective of modular design, but even loose coupling and separation of concerns can be taken to extremes. Consider the following configuration based on this chapter’s examples:
<channel id="passengers"/> <transformer input-channel="passengers" output-channel="passengersWithProfile" ref="passengerProfileEnricher" method="addProfileIfAvailable"/> <channel id="passengersWithProfile"/> <mail:header-enricher input-channel="passengersWithProfile" output-channel="flightDelays"> <mail:to expression="payload?.profile?.emailAddress"/> </mail:header-enricher> <channel id="flightDelays"/> <transformer input-channel="flightDelays" ref="flightDelayEmailGenerator" method="generateEmail" output-channel="flightDelayEmails"/> <channel id="flightDelayEmails"/> <mail:outbound-channel-adapter channel="flightDelayEmails" mail-sender="mailSender"/>
This configuration has a lot of unnecessary noise. It has many channels that are used only once and many endpoints configured to connect to those channels. Even if you rely on Spring Integration’s creation of default channels for any single component’s input-channel attribute, or for the id attribute of a channel adapter, the configura- tion is still noisy:
<transformer input-channel="passengers" output-channel="passengersWithProfile" ref="passengerProfileEnricher" method="addProfileIfAvailable"/> <mail:header-enricher input-channel="passengersWithProfile" output-channel="flightDelays"> <mail:to expression="payload?.profile?.emailAddress"/> </mail:header-enricher> <transformer input-channel="flightDelays" ref="flightDelayEmailGenerator" method="generateEmail" output-channel="flightDelayEmails"/> <mail:outbound-channel-adapter id="flightDelayEmails" mail-sender="mailSender"/>
102 CHAPTER 5 Getting down to business
This configuration allows you to remove the channel elements, but it might be even harder to follow when reading the configuration directly. Now you have to look at the various input-channel values that match with output-channel values. If the end- points were listed out of order, it would be confusing. The chain element offers a cleaner solution. Whenever default channels are being referenced by only one sub- scribing endpoint, and the flow of messages across multiple endpoints is a simple lin- ear arrangement, consider the simplification provided by the chain element (see figure 5.6).1 <chain input-channel="passengers"> <transformer ref="passengerProfileEnricher" method="addProfileIfAvailable"/> <mail:header-enricher> <mail:to expression="payload?.profile?.emailAddress"/> </mail:header-enricher> <transformer ref="flightDelayEmailGenerator" method="generateEmail"/> <mail:outbound-channel-adapter mail-sender="mailSender"/> </chain>
When creating a chain, there are a few things to consider regarding the position of endpoints. If an endpoint isn’t the last entry in a chain, then it must accept an output-channel. Those endpoints that would be playing the role of a terminating endpoint must be placed in the last position. The outbound mail adapter is an exam- ple. It couldn’t be placed in the middle of the chain because any endpoint added after it would never be invoked.
Likewise, if adding a router to a chain, it must be in the final position. Considering that the role of a router is to determine the next channel to which a message should be sent, it wouldn’t fit in any other position within a chain because all endpoints before the last one essentially have a fixed output channel.
5.6
Summary
In this chapter, we explored a number of ways Spring Integration may interact with components that are part of a business domain. Typically such components have no
1 This configuration can be used with Spring Integration 2.2 and higher. If using an earlier version, the out-
bound channel adapter must be declared outside of the chain. See https://jira.springsource.org/browse/ INT-2275 for details.
frequentFlyerService Enricher mailSender
Figure 5.6 The frequentFlyerService transformer, the header enricher, and the mailSender chained together
103
Summary
awareness of the Spring Integration API. On the contrary, the service objects can be POJOs because Spring Integration provides the necessary adapters to connect those POJOs to message channels. Those adapters also assume the responsibility of mapping between messages and domain objects. This is why we describe it as a noninvasive pro- gramming model: the methods to be adapted on the POJOs don’t need to operate with messages. Instead, those methods can expect domain objects as method parame- ters and can likewise return domain objects when invoked.
One of the benefits of this model is that preexisting business services may be con- nected to the messaging system with little effort. As a result, Spring Integration is easy to adopt incrementally. Even more important, whether connecting to preexisting ser- vices or implementing a completely greenfield application, this model makes it easy to enforce a clean separation of concerns between the business logic and the integration components. The messaging system forms a layer above the business services, and those services are easy to implement, test, and maintain without having to take any messaging concerns into account.
The message-driven interactions with a business domain featured in this chapter are all relatively simple. Even when multiple steps are necessary, such as transforming content before invoking a service, they’re in the form of linear pipelines. You learned how Spring Integration provides support for such a pipeline with the XML configura- tion of a handler chain. But sometimes the interaction with the business layer isn’t so straightforward. One of the most common requirements is to add some decision logic to determine where a particular message should go. That decision may be based on some content within the message’s payload, or it may be based on a header value that’s been added to the message.
In the next chapter, we explore Spring Integration’s support for addressing such requirements with components responsible for routing and filtering messages. Con- tinuing the theme of this chapter, we’ll see how Spring Integration promotes the sepa- ration of concerns principle. Business concerns about delivering work to certain services are decoupled from the processing that happens in those services.
104