Chapter 2. Routers and Routing

The fundamental building blocks in a Mule application are the services that are connected together using routing patterns. Whether you want your data to be passed along from one service to another, or you want a subset of your data to be passed along, or you wish to use a more complex mechanism to decide how data flows along your Enterprise Service Bus, you need to use a router. Knowing what the inbuilt routers can do for you means that you can take full advantage of them—and know what Mule's limitations are.

Note

We'll take a look at how you can extend these routers and create your own in Chapter 7.

Routing Patterns

Three principal types of routing patterns are available in Mule, as shown in Figure 2-1:

  • The inbound routers, depicted on the left, accept messages received on endpoints and handle these messages before handing them over to the service component.

  • The outbound routers, shown on the right, accept messages received from the service component and handle these messages before handing them over to the outbound endpoints.

  • Response routers are used when replies to a previous request need to be handled in some way rather than passing them all on to the originator. Typically these would be used in synchronous situations.

Routing Patterns

Figure 2.1. Routing Patterns

Inbound Routing

Routing patterns used on the inbound side handle messages that are received by a service on one of its inbound endpoints. Because the inbound router may choose to ignore, or drop, the message, not all messages received by the service will be processed by the component.

Any messages received by a service can be either messages that are intended for the service in the first place, or messages that are responses to an original request. The latter will be handled by response routers, which we will talk about later.

Every service in Mule must have an inbound router collection that contains at least one inbound endpoint. You can explicitly declare zero or more inbound routers, but if you do not explicitly declare any, a default inbound pass-through router will automatically be configured by Mule.

Idempotent Routing Pattern

The Idempotent Routing pattern is based on the pattern of the same name to allow a receiver to deal with duplicate messages. This works well in situations where a message will be delivered multiple times but handled only once. For example, in an airport the monitoring software that displays arrivals and departures may receive notification that flight RA1234 has been delayed. This message may be rebroadcast multiple times, but the software only needs to update itself once.

The router will therefore need to know which messages were received and handled already. The default behavior is to persist the unique message ID to disk, although this can be easily extended. Not all protocols support a unique ID; if this is the case the router will raise an exception.

If you don't have unique IDs (or if you'd rather impose idempotency based on other criteria), you can extend the router to note such criteria, for example fields known to be unique within the message.

<inbound>
   <!- endpoints listed here -->
   <idempotent-receiver-router
         disablePersistence="true"
         storePath="./idempotent"/>
</inbound>

The preceding inbound router configuration is for an idempotent router with the following attributes:

  • disablePersistence is meant to let Mule know if the list of message IDs can be deleted when Mule is shut down. It defaults to false, meaning that the list is persisted. If you set it to true and a duplicate message is received after the Mule server was rebooted, the message will be reprocessed.

  • storePath is the directory that will be used to store the message IDs. The default value for this field is ./.mule/idempotent/.

On the other hand, the following inbound router configuration shows an inbound idempotent router that will persist the list of message IDs and store them in the default temporary directory:

<inbound>
   <!- endpoints listed here -->
   <idempotent-receiver-router/>
</inbound>

Selective Consumer Pattern

The Selective Consumer pattern[6] allows a receiver to choose which messages to accept. The ability to ignore certain messages implies the use of some sort of filtering technique. Mule allows for a large number of filters and filter types; these will be described later on in this chapter.

Messages received by an endpoint that are going to be ignored are logged in Mule's log file. This pattern means that while an endpoint will receive x messages, the actual number of messages received by the component will be equal to or less than x.

The filtering performed by this router is applied after the data has been transformed. This can be controlled for cases when you want to filter on the original message received.

<inbound>
   <selective-consumer-router
         transformFirst="false">
      <wildcard-filter pattern="Mule*"/>
   </selective-consumer-router>
</inbound>

The preceding router illustrates a selective consumer router that is configured to work on the original, untransformed message. By default the transformFirst attribute is set to true. In this example you can see a wildcard filter that will match the payload of the message with the pattern listed. If the payload does not start with the word "Mule", the message will be rejected.

<inbound>
   <selective-consumer-router>
      <payload-type-filter expectedType=
             "com.ricstonairways.messages.Ticket"/>
   </selective-consumer-router>
</inbound>

This second router, shown in the preceding code listing, uses a payload type filter and will ignore all messages whose payload is not com.ricstonairways.messages.Ticket.

WireTap Pattern

The WireTap pattern[7] provides the ability to copy messages received on an endpoint to an alternative destination. The message will still be received by the service component (this part of the message flow is not interrupted in any way), but a copy of the message will be sent to an alternative endpoint. There could be another service component on the other side of the endpoint, of course.

The wire tap router in Mule is based on the selective consumer router, so you also have the possibility to filter messages to wire tap a subset of the messages received.

<inbound>
   <wire-tap-router>
      <payload-type-filter expectedType=
            "com.ricstonairways.messages.Ticket"/>
      <outbound-endpoint address="vm://TappedQueue"/>
   </wire-tap-router>
</inbound>

The preceding router is a wire tap inbound router that will forward messages to a VM endpoint called TappedQueue. It will only forward messages whose payload is com.ricstonairways.messages.Ticket.

<inbound>
   <wire-tap-router>
      <outbound-endpoint address="vm://TappedQueue"/>
   </wire-tap-router>
</inbound>

This second wire tap router will forward all its messages to the VM endpoint called TappedQueue.

The Response Routing Pattern

The Response Routing pattern allows a number of responses to the same request to be put together and handled as one. This is only applicable for synchronous situations, because asynchronous messages do not generate a response. Multiple responses exist because the original message would have been split up by a message splitter router; effectively this means that the router is going to join a number of forked tasks. The function of such a router is to aggregate these messages into one and pass this aggregation back as a response, but it is possible to take other actions, like choosing the most appropriate response.

<component ... >
<outbound>
      ...
</outbound>
<async-reply>
<single-async-reply-router>
         <inbound-endpoint address=
               "jms://flightResponses"/>
      </single-async-reply-router>
   </async-reply>
</service>

In Mule, the response routers are declared after the <outbound> XML tags.

Tip

The response routers handle responses, which makes them usable in synchronous situations. However, as you can see from the previous example, their names contain the word "async".

The response router configured in the preceding example listens for responses on the JMS flightResponses queue. The behavior of the single asynchronous reply router is to block the current request thread to wait for a response on an endpoint—so it returns the first response received and ignores all other replies. Thanks to this mechanism you can configure Mule to allow for forking and joining of requests in a single request thread.

Inbound vs. Outbound

The routing patterns that we've seen up until now all work with messages that are received on an endpoint. They're handled by using one or more of the available routing patterns, even if the pattern is a simple one. These messages are handled, queried, inspected, and ultimately routed to the service component if conditions are favorable. There is one key distinction to note between the response routers and a normal inbound router—a response router already knows that the messages it handles are responses, and therefore knows something about them.

On the other side of the equation, we have messages that the services will dispatch. We will handle them in the same way as we handled inbound messages—by applying some sort of routing pattern. The messages will end up on an endpoint of some sort if the conditions are right, but there is no equivalent of a response router in the outbound segment of the message flow; we don't care what sort of message we're sending.

The Chaining Routing Pattern

The Chaining pattern is not based on one of the standard Enterprise Integration Patterns (EIPs). It allows you to synchronously send a message along an endpoint and direct the response to another endpoint without performing any interim processing on the result. You can list any number of endpoints and the message flow will use them sequentially, in the order they are declared.

This is useful for situations where you have a request-response scenario (such as a web service) whose result needs to be directed to another endpoint, for example a JMS queue.

Note that if the message flow is asynchronous, the chaining router will enforce synchronicity on all further message flows until it gets to the last endpoint in its list.

<outbound>
   <chaining-router>
      <outbound-endpoint address="tcp://10.0.0.1:815"/>
      <outbound-endpoint address="stdio://OUT"
            transformer-refs="ByteStreamToString"/>
   </chaining-router>
</outbound>

The preceding chaining router has two outbound endpoints. The message received from the service component will be sent synchronously to the TCP endpoint and a reply will be expected. This reply will be directed to the next endpoint, which is the console; this endpoint also has a transformer that will convert a byte stream to a string.

Exception-Based Routing Pattern

The Exception-Based pattern is another outbound routing pattern that is not based on Enterprise Integration Patterns. Given a list of endpoints, the router attempts to route the message to the first one in the list; if this fails, it routes the message to the second one, and so on. If all routes fail an exception is raised. This router will override the synchronicity of the endpoint and force the message flow to be synchronous. It needs to do this to ensure that the message route will not fail.

This routing pattern is useful if you have backup routes to the same destination, or if you have alternative routes to take if errors are present:

<outbound>
   <exception-based-router>
      <outbound-endpoint address="tcp://10.0.0.1:1234"/>
      <outbound-endpoint address="tcp://10.0.0.2:1234"/>
      <outbound-endpoint address="vm://pendingOrders"/>
   </exception-based-router>
</outbound>

This exception-based router has three outbound endpoints. If the first TCP address is unavailable, the message will be routed on to the second IP address. If that too is unavailable, the message will be sent to the VM pendingOrders queue. If this third one fails, an exception will be raised.

The Multicasting Routing Pattern

The Multicasting pattern is also referred to as a broadcast pattern since its aim is to send the same message along multiple endpoints. The router clones the message and sends it on to each endpoint according to the synchronicity declared along that endpoint. These endpoints are listed in any order inside the Mule configuration. Take special care with any transformations, as different transformers may be required—one per endpoint, perhaps.

This pattern is based on the filtering outbound router, so you can filter to make sure only specific messages are broadcast:

<outbound>
   <multicasting-router>
      <outbound-endpoint address="tcp://10.0.0.1:1234"
         transformer-refs= "TicketToByteStream"/>
      <outbound-endpoint address="vm://pendingOrders"/>
      <payload-type-filter expectedType=
                "com.ricstonairways.messages.Ticket"/>
   </multicasting-router>
</outbound>

The preceding example shows a multicasting router. It will send the same message to the TCP endpoint as to the VM endpoint, but will need to transform the message before sending it along the TCP endpoint. The router is also configured to filter messages and will only accept com.ricstonairways.messages.Ticket objects.

Routing Options

Familiarity with routing patterns is not the only way we can control message routing. We've already seen how selective consumer routers can use filters; this section will talk about the commonly-used filter classes in Mule. We will also discuss the difference between filtering routers and endpoint filtering, how to catch messages that are going to be dropped, and how to configure responses to use response routers.

After reading this section you will be able to create Mule applications that use advanced routing mechanisms to shunt messages around.

Dropping Messages

We saw how some routers choose which messages to process and reject any messages that do not match a filtering condition. These messages are typically "lost," or "dropped," by the router and not handled at all. It is worth looking into the reason for dropped messages though, since such messages could be of significant value—perhaps they imply some sort of error or require additional processing before they can be successfully handled by this service.

In Mule these messages can be caught and handled in one of three ways: the message can be logged, forwarded to a third-party endpoint, or have some sort of customized behavior applied to it.

Catching Dropped Messages

The mechanism to catch these messages is encapsulated inside catch-all-strategy classes. The default ones are as follows:

  • <logging-catch-all-strategy> logs a copy of the message to the Mule log file before dropping the message.

  • <forwarding-catch-all-strategy> logs a copy of the message and then forwards it in its entirety to a separate endpoint.

  • <custom-forwarding-catch-all-strategy> can be extended to provide further processing after having logged and forwarded the message.

  • <custom-catch-all-strategy> lets you process messages any way you want.

<catch-all-strategy> on Routers

The following example configuration shows an inbound router collection that uses a forwarding catch-all strategy and a selective consumer router. We can see that the router selects messages based on their payload (they must be com.ricstonairways.Ticket objects), and if the message is not such an object it is forwarded to the VM errorQueue.

<inbound>
   <forwarding-catch-all-strategy >
      <outbound-endpoint address="vm://errorQueue"/>
   </forwarding-catch-all-strategy>
   <inbound-endpoint address="vm://newOrders"/>
   <selective-consumer-router>
      <payload-type-filter expectedType=
            "com.ricstonairways.Ticket"/>
   </selective-consumer-router>
</inbound>

Filters–By Payload

The <payload-type-filter> returns true if the Mule message payload matches the expected type, otherwise it returns false. If the payload is a class that is a direct descendant of the expected type, the filter will still return false because there is no exact match. For example if I am expecting java.lang.Object classes and have a message that contains a com.ricstonairways.Ticket (where the Ticket class inherits from the Object class), the filter still returns false because it is not a direct match.

The filter works by looking at the payload type, which may or may not be transformed depending on the value of the selective consumer's transformFirst attribute. These are the child attributes for this filter:

  • name should be a unique name that identifies this filter. It makes sense to use a name if the filter is defined globally.

  • not is a boolean value that inverts the filter condition.

  • expectedType is the fully-qualified class name of the expected class.

Filters–By Wildcard

The <wildcard-filter> matches the string representation of the payload against a pattern using wildcards. The asterisk wildcard is allowed and can be used as follows:

  • At the beginning of the pattern to imply that the payload must end with this pattern.

  • At the end of the pattern to imply that the payload must start with this pattern.

  • On both sides of the pattern to imply that the payload must contain the pattern.

Here are some examples:

  • *name matches any string that ends with "name"

  • name* matches any string that starts with "name"

  • *name* matches any string that contains the word "name"

You can also use multiple patterns by including them as a comma-separated list. The patterns are applied using the OR boolean operator—for example, *name, *Mule would match strings ending in "name" or "Mule".

In the following example, any messages read from the VM newOrders queue are passed through the selective consumer router and will be accepted, provided that the message contains the word "name" or contains the word "Mule" or ends in "ESB":

<inbound>
   <inbound-endpoint address="vm://newOrders"/>
   <selective-consumer-router>
      <wildcard-filter pattern="*name*,*Mule*,*ESB" />
   </selective-consumer-router>
</inbound>

Note

The Wildcard filter derives directly from java.lang.Object and implements the Filter interface directly. Consequently, it does not have a not attribute.

Logical Filters

There are three boolean filters that allow you to perform boolean AND, OR, or NOT operations on filters:

  • If you want a message to be accepted only if it matches several filters, you can use the AND filter.

  • If you want a message to be accepted only if it matches one of several filters, you can use the OR filter.

  • If you want a message to be accepted only if it does not match a specific filter, you can use the NOT filter. (However, this behavior can sometimes be achieved by using the not attribute for each router.)

The router will always see a single filter—whether it is an AND filter that contains several nested filters or not is irrelevant from the router's point of view. You can nest filters as deeply as you need to achieve your conditions, but typically they would not be more than three levels deep.

Take the following configuration code as an example:

<inbound>
   <inbound-endpoint address="vm://newOrders"/>
   <selective-consumer-router>
      <and-filter>
         <wildcard-filter pattern=
               "*First*,*Frequent*" not="true"/>
         <payload-type-filter expectedType=
               "com.ricstonairways.Ticket"/>
      </and-filter>
   </selective-consumer-router>
</inbound>

The inbound selective consumer router here will accept a message provided that both these conditions are met:

  • The message payload does not contain the words First or Frequent

  • The message payload is com.ricstonairways.Ticket

Here is another example, this time using the OR filter:

<inbound>
   <inbound-endpoint address="vm://newOrders"/>
   <selective-consumer-router>
      <or-filter>
         <wildcard-filter pattern=
               "*First*,*Frequent*" not="true"/>
         <payload-type-filter expectedType=
               "com.ricstonairways.Ticket"/>
      </or-filter>
   </selective-consumer-router>
</inbound>

The inbound selective consumer router here will accept a message provided that one of these conditions is met:

  • The message payload does not contain the word First and does not contain the word Frequent

  • The message payload is com.ricstonairways.Ticket

The last example is a copy of the previous one and wraps the result of the OR filter with a NOT filter. Note how this is different from having the not attribute enabled for each of the internal filters.

<inbound>
   <inbound-endpoint address="vm://newOrders"/>
   <selective-consumer-router>
      <not-filter>
         <or-filter>
            <wildcard-filter pattern=
                  "*First*,*Frequent*" not="true"/>
            <payload-type-filter expectedType=
                  "com.ricstonairways.Ticket"/>
         </or-filter>
      </not-filter>
   </selective-consumer-router>
</inbound>

Filters–By Message Property

If you need to filter messages based on the properties rather than the payload, the message property filter is the filter to use. You can list expressions in the form of name="value" to specify that you want to look for properties that match a specific value. This is the only way you can filter for meta-information that the underlying transport would provide. By default this filter is case sensitive, but you can turn this off.

The attributes for this filter are as follows:

  • not reverses the filter expression. By default this is set to false. However, this attribute may be dropped, so it would be best to avoid using it.

  • pattern is the pattern to search for in name="value" format.

  • caseSensitive defaults to true and lets you have case-sensitive filtering.

For example consider this configuration:

<inbound>
   <inbound-endpoint address="vm://newOrders"/>
   <selective-consumer-router>
      <or-filter>
         <message-property-filter pattern=
               "[email protected]"/>
         <message-property-filter pattern=
               "[email protected]"/>
      </or-filter>
   </selective-consumer-router>
</inbound>

We're using an OR filter to accept messages that match either message property filter. We're looking for messages that have a From field that matches the e-mail address for Ricston Airways' Arrivals department or Ricston Airways' Baggage department.

Router Collection Attributes

The only attribute that both inbound and outbound router collections have is the single matchAll attribute. By default it is set to false. It is used to indicate whether the message should be processed by all routers listed in the collection or whether the first router to accept it should be the only one to process it. Note that if multiple routers handle a message, it may be passed on to the service multiple times.

Routers, unfortunately, cannot be chained and they do not have the boolean operators that filters do. If you need to concatenate routing patterns, you will have to either create a new router or place the routers on individual services (that is, using bridge components) and feed them to one another.

Here is an example config using matchAll:

<inbound matchAll="true">
   <inbound-endpoint address="vm://newOrders"/>
   <selective-consumer-router>
      <or-filter>
         <message-property-filter pattern=
               "[email protected]"/>
         <message-property-filter pattern=
               "[email protected]"/>
      </or-filter>
   </selective-consumer-router>
   <wire-tap-router>
      <outbound-endpoint address="vm://tapped"/>
   </wire-tap-router>
</inbound>

Here we have two routers—a selective consumer router and a wire tap router. If the message properties contain a property called from that matches either or , then the message will be passed on to the service. The message will also be handed over to the wire tap router, irrespective of the result of the selective consumer router. The wire tap router will pass the message to the service after having sent it to the VM queue called tapped.

Redirecting Replies

We saw how responses can be handled by a response router in an earlier chapter and how this router can then aggregate a number of responses together. This can only be done if Mule knows that the response message(s) need to be sent to the endpoint the response router is listening on.

You can configure a <reply-to> child element for an outbound router to specify an alternative endpoint that should receive responses, as illustrated in Figure 2-2. This setting will be added to the Mule message as a property and will then be used when the response is going to be generated. All endpoints within a router will use this setting, so this applies for routers that contain multiple endpoints such as splitting routers or multicasting routers.

Redirecting Replies to a Response Router

Figure 2.2. Redirecting Replies to a Response Router

Consider the following example in which the outbound pass-through router sends messages along the newOrders VM queue and configures them so that any responses are directed to the VM responses queue. The single-async-reply router reads responses off this endpoint and handles them before returning the response to the originator.

<outbound>
   <outbound-pass-through-router>
      <vm:outbound-endpoint path="newOrders"/>
      <reply-to address="vm://responses"/>
   </outbound-pass-through-router>
</outbound>
<async-reply>
   <single-async-reply-router>
      <vm:inbound-endpoint path="responses"/>
   </single-async-reply-router>
</async-reply>

Summary

The routing patterns shown in this chapter complement the pass-through patterns seen previously. Understanding these patterns allows you to take advantage of the flexibility Mule delivers without having to code (or recode) this sort of logic within your services. You also saw the wide range of options available for routing as well as filtering possibilities.

All these allow Mule to route your data to and from the various services for you through a powerful and easily extensible series of interfaces—all done via configuration.



[6] Enterprise Integration Patterns: The Selective Consumer pattern

(http://www.enterpriseintegrationpatterns.com/MessageSelector.html)

[7] Enterprise Integration Patterns: The WireTap pattern (http://www.enterpriseintegrationpatterns.com/WireTap.html)

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset