Getting down to business
91Domain-driven transformation
5.2 Message-driven services
In the previous section, you converted data from a simple text representation to a FlightDelayEvent object that’s part of the domain model. You briefly reviewed the FlightStatusService interface that would expect such an object. Now let’s look at the implementation of that service and see how to invoke it from the integration layer. The component responsible for that invocation is a service activator.
5.2.1 The Service Activator pattern
Recall that you want to invoke the updateStatus method and that it expects a FlightDelayEvent object. The return value from that method invocation would be a FlightStatus object. Therefore, the service activator configuration would look some- thing like this:
<service-activator input-channel="flightDelays" output-channel="statusUpdates"
ref="flightStatusService" method="updateStatus"/> <beans:bean id="flightStatusService"
class="siia.business.SimpleFlightStatusService"/>
If you’ve ever worked with the Spring Framework’s JMS support, and specifically with the message-driven POJO feature, this configuration should look familiar. One of the main motivations behind Spring Integration was to provide a more generic model for building message-driven systems with the Spring JMS support serving as a precedent. The feature to be supported is still message-driven POJOs. In this case, the POJO is the Spring-managed flightStatusService object. But instead of a JMS message, it’s a simple Spring Integration message being mapped to the domain object expected by that service. The source of that message could be any code that sends to that channel or any adapter that’s connected to that channel.
You may also have noticed that the service defines a return value, so a correspond- ing output-channel is declared on the service activator configuration element. That output channel might be connected to some external system by a channel adapter, or it might refer to the input channel of another component, such as a router, a splitter, or another service activator.
It’s common for the output of one service activator to refer to the input channel of another service activator, and that approach can be used to connect multiple services into a pipeline or chain. Figure 5.4 shows three services that are connected in such a chain.
As we discuss later in this chapter, chaining services like this is such a common technique that Spring Integration provides an even simpler way to define such chains without having to explicitly define each channel.
5.2.2 The Return Address pattern
The chained services model works well for linear use cases where a sender on one end triggers the message flow but doesn’t expect a return value. In such cases, there is an
95
Message-driven services
ultimate receiver at the other end of the chain. Either the final service to be invoked doesn’t return a value or the chain ends with a channel adapter such as a file writer. In other use cases, the original sender does expect a reply. In those cases, a header can be provided on the original message with a reference to a reply channel. In enterprise integration pattern (EIP) terminology, that header represents the return address. Here’s an example of a sender that provides such a header and then waits for the response:
RendezvousChannel replyChannel = new RendezvousChannel(); Message requestMessage = MessageBuilder.withPayload(someObject)
.setReplyChannel(replyChannel).build(); requestChannel.send(requestMessage);
Message replyMessage = replyChannel.receive();
Before going any further with this example, we should point out that you’ll probably never write code like this because Spring Integration provides support for the reply channel configuration behind the scenes. You’ll see an example of this in the upcom- ing section on the messaging gateway support. For now, we want to consider the impli- cations of the reply channel header for a service activator. If the requestChannel in the preceding snippet were connected to the flightStatusService object, then the configuration would be similar to the previous service activator example. The key dif- ference would be the absence of an output-channel. By leaving that out, you force the service activator to store the service’s return value in the payload of a message that it then sends to the channel referenced by the replyChannel header. The configura- tion would look like this:
<service-activator input-channel="flightDelays" ref="flightStatusService" method="updateStatus"/> <beans:bean id="flightStatusService" class="siia.business.SimpleFlightStatusService"/> Service 3 Service 2 Service 1
Figure 5.4 Pipeline configuration with three services. A message is deconstructed and reconstructed at each service activator. The payload is fed into the service, and the return value is used to construct a new message. The headers of the message remain the same, so the overall context is preserved throughout the pipeline.
96 CHAPTER 5 Getting down to business
The configuration is almost identical to that in the previous version, and more impor- tant, no changes are required to the service code. Switching between a chained- services and a request-reply interaction model is as simple as including or omitting the output channel from the configuration, respectively. Keep in mind that if a service does return a value, and neither the output channel has been configured nor the reply channel header provided, an exception would be thrown at runtime because the service activator component wouldn’t know where to send the return value. If a ser- vice does return a value, yet you intentionally want to ignore it, you can provide nullChannel as the output-channel attribute’s value.