Go beyond sequential processing:
6.2 Whose message is this, anyway?
6.2.2 Routers provided by the framework
In our example, you saw a router at work. The router in the example decided where to send messages according to the payment option selected by the user, which consti- tutes the payload type of the payment message. Using the terminology of Enterprise Integration Patterns, all the routers provided by the framework are content-based routers. This means the decision on where to send the message next is based solely on the con- tents of the message.
DEFAULT ROUTERS
The PaymentSettlement router you saw earlier is an example of using a default router. The simplest router is created by defining a <router/> element. When you define a router, bear in mind the following:
It must describe how the routing decision is made—it must indicate who is responsible for evaluating the message and determining the next channel. The decision may come in the form of a channel or channel name; in the latter
case, the channel names must be converted to channel instances.
When it comes to making a decision, there are two possible variants: either delegate to a method defined on a POJO, or use a SpEL expression. The previous router example showed how to implement a router using a POJO. Now it’s time to look at some other variants.
To be usable for the routing logic, a method definition’s arguments must comply with the same general requirements as business services and service activators: either take as argument a message or take as argument an object representing the payload and/or a number of @Header-annotated parameters. The return type of the method must be a message channel, a string representing a message channel name, a collec- tion of strings or message channels, or an array of strings or message channels. The latter are supported because routers can return multiple values, and the next section provides an example of how that works.
If the method returns channel names, the names must be converted into channel instances. For that, you need to provide a channel resolver. If you don’t want to provide one, the framework, by default, will provide one that looks up channels by their IDs in the application context. Deciding whether or not to use an explicitly configured chan- nel resolver largely depends on how keen you are on using channel names inside your routing logic.
115
Whose message is this, anyway?
You can always resort to this option for configuring a router, but there are other options that don’t require creating a new implementation every time you want to con- figure a router. The framework provides implementations that cover certain common use cases.
PAYLOAD-TYPE ROUTERS
The router you implemented for PaymentSettlement instances takes into account the type of the object when deciding the next target channel. In your router implementa- tion, you checked that yourself, but instead of doing that, you could’ve used an imple- mentation provided by the framework. You could’ve written this:
<channel id="payments"/> <payload-type-router input-channel="payments"> <mapping type="siia.booking.domain.payment.CreditCardPayment" channel="credit-card-payments"/> <mapping type="siia.booking.domain.payment.Invoice" channel="invoices"/> <mapping type="siia.booking.domain.payment.PaypalPayment" channel="paypal-payments"/> </payload-type-router> <channel id="invoices"/> <channel id="paypal-payments"/> <channel id="credit-card-payments"/>
As you can see in this example, whenever the next channel can be decided by the type of the message payload, you can use a <payload-type-router/> out of the box instead of implementing that logic yourself.
HEADER VALUE ROUTERS
If the routing information (for example, the target channel) can be found in one of the headers of the message, you can use a header value router to simplify the configuration:
<header-value-router input-channel="payments" header-name="PAYMENT_PROCESSING_DESTINATION"/>
Here, the PAYMENT_PROCESSING_DESTINATION header defines the next destination of the message. The general idea behind routing based on header values is that the rout- ing information can’t be easily found in the message itself, but is added to the message earlier in the message flow. A header value router pairs well with a header enricher to populate the relevant header of the message:
<header-enricher input-channel="payments"
output-channel="enriched-payments">
<header name="PAYMENT_PROCESSING_DESTINATION" ref="enricher" method="determineProcessingDestination"/>
</header-enricher>
<header-value-router input-channel="enriched-payments" header-name="PAYMENT_PROCESSING_DESTINATION"/>
116 CHAPTER 6 Go beyond sequential processing: routing and filtering
Payload-type routers and header value routers are simplifications that cover particular use cases. Another way to set up a router without writing a new class is to use SpEL expressions.
ROUTING BY SPEL EXPRESSIONS
If the routing decision can be made through a simple evaluation of the message instead of by creating a separate POJO implementation and delegating to it, you can embed a SpEL expression into the router. This works well when the outcome of the expression evaluation matches the names of the target channels.
Let’s take another example. As you saw previously, all credit card processing requests are routed to the same channel, credit-card-payments. But different credit cards may be processed differently because they’re handled by different issuing orga- nizations. You can extend the routing logic by adding another router to deal with credit card payments specifically, as follows:
<channel id="credit-card-payments"/> <router input-channel="credit-card-payments" expression="payload.creditCardType"/> <channel id="VISA"/> <channel id="AMERICAN_EXPRESS"/> <channel id="MASTERCARD"/>
This example doesn’t use a separate class to implement the routing logic. Instead, a SpEL expression is evaluated against the payload. In this case, you evaluate the creditCardType property of the payload (which is of the type CreditCardPayment). The outcome of the evaluation may be VISA, AMERICAN_EXPRESS, or MASTERCARD, which are also the names of the potential channels.
In all the examples so far, the routing logic returns the name of the next destina- tion channel. In most situations, this works well, but relying on the channel names to be fixed may prove to be a problem in the long run. You can introduce another level of indirection by allowing mapping the string values returned by the routing logic to channels from the configuration.
CHANNEL RESOLUTION AND YET ANOTHER LEVEL OF INDIRECTION2
A strategy for developing more reusable application components is not to rely on your routing logic to return channel names but instead to use placeholder values that con- vey the logical significance of the routing process (“Whenever the payment will be set- tled through VISA credit card payment, send it to the channel for VISA payments, whichever name it has.”).
2 As of version 2.1, support for this type of channel resolution has been deprecated, and it will likely be removed
altogether in version 3.0. The rationale for its removal is that the ability to specify a SpEL expression in a router provides more than enough flexibility. For example, it would be trivial to provide an expression that appends a suffix. Considering SpEL expressions may now reference any bean using @, you can even use an expression to do something like this: @myChannelResolver.resolve(headers.paymentType).
117
Whose message is this, anyway?
In this case, the router can be supplied with a ChannelResolver that will take care of translating the string value provided by the routing logic into an actual channel instance: <channel id="VISA-payments"/> <channel id="AMERICAN_EXPRESS-payments"/> <channel id="MASTERCARD-payments"/> <router expression="payload.creditCardType" channel-resolver="creditCardPaymentsChannelResolver" input-channel="credit-card-payments"/> <beans:bean id="creditCardPaymentsChannelResolver" class="siia.booking.integration.routing. ➥CreditCardPaymentChannelResolver"/>
In the previous example, the names of the channels had to match the potential results of evaluating the routing expression, but introducing the ChannelResolver gives you more liberty in defining your configuration. Adding a suffix may not seem a signifi- cant change to the previous case, but the destination channels have different names than the result of evaluating the expression, and it is the role of the ChannelResolver to bridge the gap.
All the examples so far are based on the assumption that the routing scenario implies a single next destination channel. But routing can also enable a message to be resent to a number of other channels, as discussed next.