• No results found

Do you want to get this message?

In document Spring Integration in Action (Page 136-141)

Go beyond sequential processing:

6.1 Do you want to get this message?

The main role of an endpoint is to consume messages from a channel and process them by invoking the associated MessageHandler. This means the service is invoked for every message that arrives on the inbound channel.

It’s possible that a given consumer isn’t interested in all the messages it receives. For example, in a publish-subscribe scenario, different consumers may have different interests in the incoming messages even if they’re all potential recipients.

To enable the selective consumption of messages, you can use a special type of message handler, a message filter. As you can see in figure 6.1, the filter is a message handler that evaluates messages without transforming them and publishes back to an output channel only the ones that satisfy a given rule.

Of course, the decision of whether a certain component can process certain types of messages can be made by the component itself. For example, the plain old Java object (POJO) implementation of a service activator can decide whether messages are valid. Embedding this decision into the component works well if the decision is based on a broad general principle that’s applicable everywhere that component is used. Filters are useful wherever the filtering condition is based on an application-specific require- ment that’s not inherently linked to the Java implementation of the business service. 6.1.1 Filtering out messages

The application can receive cancellation requests through a gateway. The requests are forwarded to a cancellation processing service, and further to a notification service, which sends out confirmation emails. Cancellations for different types of reservations may need to be processed differently because the conditions may be different (refund policies, advance notice requirements, and so on).

The example cancellations processor knows only how to process Gold cancella- tions, so you can discard everything else, as in the following example:

Filter

C B A A

A A

Consumer

Figure 6.1 A message filter evaluates incoming messages. Only A’s are published to the next channel, whereas B’s and C’s are discarded.

106 CHAPTER 6 Go beyond sequential processing: routing and filtering <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/integration" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/ ➥spring-integration.xsd"> <channel id="input"/> <channel id="validated"/> <channel id="confirmed"> <queue/> </channel> <channel id="rejected"> <queue/> </channel> <gateway id="cancellationsGateway" service-interface="siia.booking.integration.cancellation. ➥CancellationsGateway" default-request-channel="input"/> <filter id="cancellationsFilter" input-channel="input" ref="cancellationsFilterBean" method="accept" discard-channel="rejected" output-channel="validated"/> <beans:bean id="cancellationsFilterBean" class="siia.booking.integration.cancellation. ➥CancellationRequestFilter">

<beans:property name="pattern" value="GOLD[A-Z0-9]{6}+"/> </beans:bean> <service-activator id="goldCancellationsProcessor" input-channel="validated" ref="cancellationsService" method="cancel" output-channel="confirmed"/> <beans:bean id="cancellationsService" class="siia.booking.domain.cancellation. ➥StubCancellationsService"/> </beans:beans>

Note the <filter/> element used for inserting the filter between the gateway and the service activator that processes the cancellation requests. From a syntax perspective, its definition isn’t very different from that of other message handlers discussed so far, such as the transformer and the service activator. It has an input channel for handling incoming messages, an output channel for forwarding messages, and delegates to a Spring bean for the actual processing logic. But it's semantically different. Messages don’t suffer any transformation when they pass through this component; the same message instance that arrives on the input channel is forwarded to the validated channel if it passes the filtering test.

107

Do you want to get this message?

In our application, a cancellation request that can be processed by the service must contain a reservation code that corresponds to a Gold reservation. Of course, this tells nothing about whether a reservation with that code exists, and we won’t try to do all the validation at this point (many things can be wrong with the request itself, and dealing with such errors is the responsibility of the cancellation service). But, at a minimum, you know that reservation codes have to conform to a certain standard pat- tern, and you can do a quick test for that. Here’s how the implementation of the filter- ing class looks:

package siia.booking.integration.cancellation;

import siia.booking.domain.cancellation.CancellationRequest; import java.util.regex.Pattern;

public class CancellationRequestFilter { private Pattern pattern;

public void setPattern(Pattern pattern) { this.pattern = pattern;

}

public boolean accept(CancellationRequest cancellationRequest) { String code = cancellationRequest.getReservationCode(); return code != null && pattern.matcher(code).matches(); }

}

As with the service activator and the transformer, you can implement the logic in a POJO (and we strongly recommend you do so). The filtering logic consists of a method that takes as argument the payload of a message (as in the previous example), one or more message headers (using the @Header/@Headers annotation), or even a full-fledged message, and returns a boolean indicating whether the message will pass through.

WHERE DO REJECTED MESSAGES GO?

In the simplest case, which is also the default, messages are just discarded (or, for a UNIX-based analogy, /dev/null). If you don’t want them to be discarded, you have two other options:

 You can specify a channel for the discarded messages. In this case, rejection is more a form of redirection, allowing the application to handle them further as regular messages. From the point of view of the framework, they’re still mes- sages and therefore subject to any handling a message on a channel can undergo.

 You can instruct the framework to throw an exception whenever a message is rejected.

The framework allows both options to be enabled at the same time, but from a practi- cal perspective, they’re mutually exclusive. Nevertheless, when both options are active, the message is sent on the discarded messages channel before the exception is actu- ally thrown (an important detail when a synchronous channel strategy is in place).

108 CHAPTER 6 Go beyond sequential processing: routing and filtering

To illustrate, here’s a more elaborate variant of the previous example, where rejected cancellation requests are redirected on a specific channel and from there are forwarded to an outbound notification system. Assuming the cancellation request contains enough information about the requester itself, so that a reply message can be sent, you can implement the filter this way:

<filter id="cancellationsFilter" input-channel="input" discard-channel="rejected" ref="cancellationsFilterBean" method="accept" output-channel="validated"/> <bean id="cancelationsFilterBean" class="siia.booking.integration ➥.cancellation.CancellationRequestFilter"> <property name="pattern" value="GOLD[A-Z0-9]{6}+"/>

</bean> <!-- other definitions --> <channel id="input"/> <channel id="validated"/> <mail:header-enricher input-channel="rejected" output-channel="mailTransformer"> <mail:to expression="payload.requestor?.emailAddress"/> </mail:header-enricher> <transformer input-channel="mailTransformer"

expression="payload.reservationCode + ' has been rejected'" output-channel="rejectionMail"/>

<mail:outbound-channel-adapter id="rejectionMail"

mail-sender="mailSender"/>

Or if you want to throw an exception instead, you can write this:

<filter id="cancellationsFilter" input-channel="input" throw-exception-on-rejection="true"

ref="cancellationsFilterBean" method="accept" output-channel="validated"/>

USING EXPRESSIONS

As you’ve seen, it’s simple enough to implement the filtering logic as a POJO. But in a lot of common cases, you don’t even need to do that. If all the information is to be found in the message itself, and all you need is to write a logical expression that’s com- puted against the payload or the header values, you can use the Spring 3.0 Expression Language (SpEL) directly. Instead of defining a distinct CancellationRequestFilter, you can get the same result by using the following filter definition:

<filter id="cancellationsFilter" input-channel="input" discard-channel="rejected"

expression="payload?.reservationCode matches 'GOLD[A-Z0-9]{6}+'" output-channel="validated"/>

This way, there’s no need for a distinct bean to implement the decision logic. The advantage of using this filter definition is that the filtering logic can quickly be viewed in the context of the message flow. The disadvantage of using SpEL expressions directly is that they’re harder to test in isolation from the filter itself, so you should take care to call out to the proper abstractions if the logic gets complicated.

109

Do you want to get this message?

Having two options on hand, when should you create a distinct implementation, and when should you use an inline expression? An inline expression is simple enough, and could be externalized easily (using, for example, a Property- PlaceholderConfigurer). But it’s not flexible or reusable across the application. Our recommendation for making a decision in this case is to use expressions whenever the condition is based on the attributes of the message itself. In more complicated cases, when the decision involves a sophisticated algorithm, or the sender must consult with other collaborating components, you may want to create a standalone implementa- tion and take advantage of Spring’s dependency injection capabilities.

If you really want to have your cake and eat it too, there’s a hybrid solution: use a SpEL expression, but delegate (part of) the logic to a Java object. One powerful and simple way to do this is to delegate the decision to the message payload or a header. This requires the message to carry a domain object:

<filter id="cancellationsFilter" input-channel="input" discard-channel="rejected"

expression="payload.isGold()" output-channel="validated"/>

Weighing the pros and cons of each option is something you’ll have to do again for each situation. The framework will support whatever decision you make in the end.

Now that you have some tools, let’s look at some uses for them. 6.1.2 Using filters for selective processing

Let’s get a bit of perspective here: how do messages that don’t satisfy the criteria to be processed get on the inbound channel, anyway? Wouldn’t it be simpler if their pro- ducers didn’t bother to send messages that won’t be processed?

Often, filters are used in combination with publish-subscribe channels, allowing multiple consumers with different interests to subscribe to a single source of informa- tion and to process only the items they’re really interested in. Such a solution doesn’t preclude multiple components receiving a message at the same time, but the compo- nents have complete control over what they can and can’t process.

Figure 6.2 shows such an example. Three different components with different interests subscribe to the same channel. It’s much easier if a producer knows only about a single destination channel for its messages.

Only A Only B B and C C B A A A A Consumer 1 Consumer 2 B B C Consumer 3 Consumer 2 Consumer 3

Figure 6.2 A publish-subscribe channel and filters combination for selective processing. The first subscriber is interested only in A’s; the second, only in B’s; and the third, in B’s and C’s.

110 CHAPTER 6 Go beyond sequential processing: routing and filtering

Message filters can decide whether a message will be forwarded to a next channel. If you decide to configure them with a channel for discarding messages, message filters will act as switches, choosing one channel or another depending on whether the mes- sage is accepted or discarded. In that case, they’re a simplified form of a more general type of component that can decide from among multiple destinations where a message should go next. This component is the message router, which is the focus of the second part of the chapter.

In document Spring Integration in Action (Page 136-141)