Exploring ESB Patterns with Mule Exploring ESB Patterns with Mule

by Igor Dayen
07/31/2007

The article gives a practical introduction into development with Mule, a popular open source messaging framework. First, we will briefly overview Enterprise Service Bus concepts and will provide an introduction to the Mule programming model. Next, we present an example employing the Routing Slip pattern, as described in Enterprise Integration Patterns by Gregor Hohpe and Bobby Woolf, and demonstrate how this example can be implemented using Mule framework. We will walk through the relevant Mule configuration file and the classes used by implementation. Finally, we will show how to install and run this example.

Enterprise Service Bus Overview

According to Mark Richards, authority on the subject, in his presentation "The Role of Enterprise Service Bus," ESB can be viewed as a set of components that provide the business tier with core integration services:

ESB evolved to be the next generation of enterprise integration solutions, with message-oriented middleware (MOM) and web services being considered the closest relatives. However, ESB has a broader scope than MOM. For instance, ESB includes such services as coordination of business processes, or protocol transformation, that normally lay out of scope of MOM. Protocol transformation is one of the key features of ESB, aimed at providing communication between various incompatible systems without writing (otherwise necessary) adapters. For example, ESB can provide a MQ Series-to-WAP transformation facility for publishing mortgage rates on mobile devices. In contrast, MOM usually supports a single transport mechanism, and its services, therefore, significantly rely on underlying message protocols, messaging data formats, error handling mechanisms, and so on.

ESB also has a broader scope than web services. For instance, web services frameworks usually lack such features as publish-subscribe messaging models (the WS-Notification family of protocols is a fairly new development in this field) or protocol transformation.

The comparison matrix below highlights some of the feature differences between JMS, web services, and ESB (of course, this table matrix is not a complete feature comparison).

Feature Comparison Matrix: ESB Versus JMS and Web Services

ESB MOM Web Services
Transport layer JMS provider, SOAP provider, WSDL provider, Stream provider, File provider, and more JMS SOAP over HTTP
Mediation and rules Protocol translation, routing, and data transformation Routing and data transformation Data transformation
Deployment Maximum level of flexibility for dynamic configuration Requires recompiling client code Requires recompiling stubs

There are a number of ESB implementations currently available: Mule, ServiceMix, and Sonic ESB, just to name a few. In this article we will use Mule as the implementation platform. This choice is motivated by:

Note that Mule is licensed under the MuleSource Public License, which is a modification of the Mozilla Public License Version 1.1.

The Mule Programming Model

Mule is described by its designers as a lightweight messaging framework that manages communications between distributed client service components, named as Universal Messaging Objects (UMOs). A UMO is just a plain old Java object (POJO) that receives and processes messages, and communicates with the rest of the Mule-managed service components via inbound and outbound "messaging endpoints"--transport configuration components that specify such characteristics as transport protocol and communication address. In our example, we use simple transport mechanisms such as system input/output and intra-VM communication channels. Outbound endpoints are where Mule dispatches messages to another service component. UMOs are attached to endpoints by means of inbound and outbound routers. Outbound routers could include filtering facilities in order to channel outbound messages to an appropriate endpoint.

Inbound and outbound messages could also undergo transformation by an appropriate transformer attached to the UMO. An inbound transformer provides message transformations from the transport format into the client format immediately prior to UMO method invocation, while the outbound transformer provides message transformations from the client format into transport format and sits in between the outbound message router and the message endpoint.

Messages can also be intercepted in order to provide such additional services as logging, collecting statistics, etc. Figure 1 illustrates the data flow from inbound to outbound endpoints as presented in the Mule Programmers Guide.

Message flow from inbound endpoint to outbound endpoint
Figure 1. Message flow from inbound endpoint to outbound endpoint

Invaluable resources on Mule concepts and architecture can be found at the Codehaus repository of open source projects that hosts Mule. You may be interested in checking out the reference documentation and other information in the Resources section.

Here is a summary of Mule concepts that would be required to understand this article's routing slip example.

UMO (Universal Messaging Object)
Event-driven client software component that receives and processes a message
Inbound transformer
Component that provides message transformations from the transport format into the client format immediately prior to UMO method invocation
Outbound transformer
Component that provides message transformations from client format into transport format and sits in between outbound message router and message endpoint
Endpoint
As per the Mule Programmers Guide, this is a "configuration object that defines how data will be received and transformed by Mule. On the endpoint, you can configure the endpoint address, transport-specific configuration information, transactions, and filters."
Inbound router
Component that controls message flow in the endpoint-to-UMO direction
Outbound router
Component that controls message flow in the UMO-to-endpoint direction; may include various kinds of filters to test whether the message can be sent to a given endpoint
Interceptor
Provides additional services, like message logging, collecting statistics, etc.
Connector
As per the Mule Architecture Guide, "the connector provides the implementation for connecting to the external system"

Example: Routing Slip Pattern

Let's examine a classic "problem escalation" example. Let's assume a customer experiences email-settings problems with an internet services provider. The customer fills in a form, where he or she specifies the product, the problem category, the problem subcategory, and finally, the "terminal" case within the subcategory. Our customer is using ADSL, and experiences an email server settings problem. Hence, the form includes the list of keywords in this order: "ADSL, Email, Server Settings." The customer submits the form, and the request is processed by a product identification node, then by an ADSL node, then by an email node, and finally by a server settings processing node. This request escalation flow is illustrated in Figure 2.

Request escalation using the Routing Slip pattern
Figure 2. Request escalation using the Routing Slip pattern

The pattern when the message (the message header or its body) includes the sequence of processing nodes is known as a routing slip. Each router directs the message to the next channel identified by the next key in the routing slip. The Enterprise Integration Patterns site has an excellent Routing Slip explanation:

"How do we route a message consecutively through a series of processing steps when the sequence of steps is not known at design time and may vary for each message?

Attach a Routing Slip to each message, specifying the sequence of processing steps. Wrap each component with a special message router that reads the Routing Slip and routes the message to the next component in the list.

We insert a special component into the beginning of the process that computes the list of required steps for each message. It then attaches the list as a Routing Slip to the message and starts the process by routing the message to the first processing step. After successful processing, each processing step looks at the Routing Slip and passes the message to the next processing step specified in the routing table."

Routing Slip Example Implementation

Mule Configuration

A configuration file defines how all Mule components fit together. In our example, the configuration consists of the following sections:

The connector in this example specifies the Mule class that is used for reading from the standard input stream and writing into the standard output stream. The connector also defines the prompt for entering an input message as follows:


    <connector name="SystemStreamConnector"
    className="org.mule.providers.stream.SystemStreamConnector">
        <properties>
            <property name="promptMessage"
            value="Format: route=address1:...addressN,acct=account,app=application"/>
            <property name="messageDelayTime" value="1000"/>
        </properties>
    </connector>

Endpoints are defined within the tag endpoint-identifiers. We use system input and output as terminal endpoints. ProductUMO, the starting UMO in the chain, refers to the input endpoint as stream://System.in. Similarly, EmailSettingsUMO, the terminal UMO in the chain, refers to the output endpoint as stream://System.out. "Virtual machine" endpoints are used as intermediate ones within the JVM, as follows:

 
  <endpoint-identifiers>
            <endpoint-identifier name="ADSL" value="vm://ADSL"/>
            .....................................................
            <endpoint-identifier name="EMAIL-LOGIN" value="vm://EMAIL-LOGIN"/>
   </endpoint-identifiers>

An inbound transformer transforms an inbound message into a format that can be consumed by a UMO as input. In the configuration fragment below, InPropertiesTransformer translates a message from a String into a java.util.Properties object. An outbound transformer transforms a message produced by a UMO in the opposite direction, from Properties into a String:


     <transformers>
        <!-- Transforms message from key-value pairs into string -->
        <transformer name="OutPropertiesTransformer" className="com.objcentric.messaging.mule.transformers.OutPropertiesTransformer"
                        returnClass="java.lang.String"/>
        <!-- Transforms message into key-value pairs -->
        <transformer name="InPropertiesTransformer" className="com.objcentric.messaging.mule.transformers.InPropertiesTransformer"
                        returnClass="java.util.Properties"/>
    </transformers>

We are also using standard Mule interceptors for logging and tracing:


    <interceptor-stack name="default">
         <interceptor className="org.mule.interceptors.LoggingInterceptor"/>
         <interceptor className="org.mule.interceptors.TimerInterceptor"/>
    </interceptor-stack>

Components, described above, are glued together in the section model, which lists the set of UMOs. The Mule UMO component defines inbound/outbound transformers and inbound/outbound routers. Here is the ProductUMO definition:


        <mule-descriptor name="ProductUMO" 
                        implementation="com.objcentric.messaging.mule.processors.PassThroughProductProcessor"
                        outboundTransformer="OutPropertiesTransformer">
            <!-- any number of endpoints can be added to an inbound router -->
            <inbound-router>
                <endpoint address="stream://System.in" transformers="InPropertiesTransformer"/>
            </inbound-router>
            <!-- 
            The OutboundPassthroughRouter is a router that automically sends every
            message it receives -->
            <outbound-router>
                <catch-all-strategy className="org.mule.routing.LoggingCatchAllStrategy"/>
                <router className="org.mule.routing.outbound.FilteringOutboundRouter">
                    <endpoint address="ADSL"/>
                    <filter pattern="*ADSL*" className="org.mule.routing.filters.WildcardFilter"/>
                </router>
                <router className="org.mule.routing.outbound.FilteringOutboundRouter">
                    <endpoint address="CABLE"/>
                    <filter pattern="*CABLE*" className="org.mule.routing.filters.WildcardFilter"/>
                </router>
             </outbound-router>
            <interceptor name="default"/>
        </mule-descriptor>

As seen in this configuration fragment, ProductUMO is implemented by the class PassThroughProductProcessor, which consumes input messages translated by the inbound transformer. This transformer is referred to by the inbound-router tag, while the outbound transformer is referred to, alongside the implementation class definition, within the tag mule-descriptor on the top level. Note that both transformers can be referred from the "top" level; see, for instance, EmailSettingsUMO in the source code at the end of this article. Finally, the outbound-router lists outbound routers that apply pattern matching filters for directing a message to the appropriate endpoint.

Custom Classes

As per Mule architecture, in order to implement request escalation processing logic, we have to develop a set of custom UMOs. Here is the chain of UMOs, starting with the PassThroughProductProcessor UMO, where each UMO in the chain corresponds to the processing node, as per Figure 1:


        PassThroughProductProcessor (Product UMO) -->
                PassThroughADSLProcessor (ADSL UMO) -->
                        PassThroughEmailProcessor (Email UMO) -->
                                EndPointEmailProcessor (Email Settings UMO)


Each UMO validates the message--an account number in our example--and passes the valid message to the next processing stage. The last UMO in this chain generates email server settings. We use the method process as a UMO invocation point. So how does Mule locate the appropriate UMO method? In our example, we use a Mule feature to determine the methods by matching the method argument with the type returned by an inbound transformer attached to a UMO.

InPropertiesTransformer transforms a message into a java.util.Properties object, the type that is used as an argument by the process method. In fact, there are other methods provided by Mule to determine a method being called on UMO; for example, a UMO could comply with interface Callable, or the method argument could be of the type org.mule.umo.UMOEventContext or java.util.Event. In any case, the determination is done by the Mule class DynamicEntryPointResolver. Once this method is called, the outbound message is transformed accordingly from Properties into String.

We use the standard Mule router, FilteringOutboundRouter, in order to route messages by applying pattern matching on the message body. For instance, if the message body contains the keyword "ADSL", then the outbound router attached to the product UMO will route message to the "ADSL" endpoint, the one that serves as an input endpoint for the ADSL UMO. Unmatched messages are captured by a catch-all-strategy processor, which just stops further message processing and logs the message.

The source code, configuration file, and batch file for this example are available in the routingslip.zip file (see Resources).

Installation and Example Execution

Mule Installation

Example Installation

Unzip the sample code attached to this article and drop it under mule-1.3-rc2\samples.

Run Example

To execute the example, run the command routing-slip.bat from the mule-1.3-rc2\samples\routingslip\bin directory.

If you enter ADSL:EMAIL:EMAIL-SERVER-SETTINGS,acct=1234 after the prompt, you'll get the following console output:


C:\etc\mule-1.3-rc2\samples\routingslip\bin>routing-slip.bat
MULE_HOME=..\..\..

**********************************************************************
* Mule - Universal Message Objects version 1.3-rc2                   *
* SymphonySoft Limited                                               *
* For help or more information go to http://www.muleumo.org          *
*                                                                    *
* Server started: Sunday, October 15, 2006 5:24:52 PM EDT        *
* JDK: 1.4.2_10 (mixed mode)                                         *
* OS: Windows 2000 - Service Pack 4 (5.0, x86)                       *
* Host: OBJECTCENTRIC (192.168.1.100)                                *
* ID: Mule_Routing_Slip_Sample                                       *
*                                                                    *
* Agents Running:                                                    *
*   Mule Admin: accepting connections on tcp://localhost:60504       *
**********************************************************************

Format: route=address1:...addressN,acct=account,app=applicationroute=ADSL:EMAIL:
EMAIL-SERVER-SETTINGS,acct=1234

DEBUG 2006-10-15 17:25:01,744 [ProductUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  No CorrelationId is set on the message, will set a new Id
DEBUG 2006-10-15 17:25:01,744 [ProductUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  Extracted correlation Id as: 9df2bbb0-5c93-11db-be39-91271a7aa7db
DEBUG 2006-10-15 17:25:01,754 [ProductUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  Setting Correlation info on Outbound router for endpoint: vm://ADSL
Id=9df2bbb0-5c93-11db-be39-91271a7aa7db
DEBUG 2006-10-15 17:25:01,764 [ProductUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  Message being sent to: vm://ADSL
DEBUG 2006-10-15 17:25:01,764 [ProductUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  org.mule.providers.DefaultMessageAdapter{id=9df2bbb0-5c93-11db-be39-91271a7aa7db,  payload=java.util.Properties, correlationId=9df2bbb0-5c93-11db-be39-91271a7aa7db,  correlationGroup=-1, correlationSeq=-1, encoding=UTF-8, exceptionPayload=null, properties={
MULE_CORRELATION_ID=9df2bbb0-5c93-11db-be39-91271a7aa7db
}}
DEBUG 2006-10-15 17:25:01,804 [ProductUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  Message payload: 
{route=ADSL:EMAIL:EMAIL-SERVER-SETTINGS,acct=1234}
DEBUG 2006-10-15 17:25:01,924 [ADSLUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  CorrelationId is already set to '9df2bbb0-5c93-11db-be39-91271a7aa7db' , not setting it again
DEBUG 2006-10-15 17:25:01,924 [ADSLUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  Message being sent to: vm://EMAIL
DEBUG 2006-10-15 17:25:01,924 [ADSLUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  org.mule.providers.DefaultMessageAdapter{id=9df2bbb0-5c93-11db-be39-91271a7aa7db,  payload=java.util.Properties, correlationId=9df2bbb0-5c93-11db-be39-91271a7aa7db,  correlationGroup=-1, correlationSeq=-1, encoding=UTF-8, exceptionPayload=null, properties={
MULE_ENDPOINT=vm://ADSL
MULE_CORRELATION_ID=9df2bbb0-5c93-11db-be39-91271a7aa7db
}}
DEBUG 2006-10-15 17:25:01,924 [ADSLUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  Message payload: 
{route=ADSL:EMAIL:EMAIL-SERVER-SETTINGS,acct=1234}
DEBUG 2006-10-15 17:25:01,934 [EmailUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  CorrelationId is already set to '9df2bbb0-5c93-11db-be39-91271a7aa7db' , not setting it again
DEBUG 2006-10-15 17:25:01,934 [EmailUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  Message being sent to: vm://EMAIL-SERVER-SETTINGS
DEBUG 2006-10-15 17:25:01,934 [EmailUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  org.mule.providers.DefaultMessageAdapter{id=9df2bbb0-5c93-11db-be39-91271a7aa7db,  payload=java.util.Properties, correlationId=9df2bbb0-5c93-11db-be39-91271a7aa7db,  correlationGroup=-1, correlationSeq=-1, encoding=UTF-8, exceptionPayload=null, properties={
MULE_ENDPOINT=vm://EMAIL
MULE_CORRELATION_ID=9df2bbb0-5c93-11db-be39-91271a7aa7db
}}
DEBUG 2006-10-15 17:25:01,934 [EmailUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  Message payload: 
{route=ADSL:EMAIL:EMAIL-SERVER-SETTINGS,acct=1234}
DEBUG 2006-10-15 17:25:01,954 [EmailSettingsUMO.2]  org.mule.routing.outbound.OutboundPassThroughRouter: CorrelationId is already set to  '9df2bbb0-5c93-11db-be39-91271a7aa7db' , not setting it again
DEBUG 2006-10-15 17:25:01,954 [EmailSettingsUMO.2]  org.mule.routing.outbound.OutboundPassThroughRouter: Message being sent to: stream://System.out
DEBUG 2006-10-15 17:25:01,954 [EmailSettingsUMO.2]  org.mule.routing.outbound.OutboundPassThroughRouter:  org.mule.providers.DefaultMessageAdapter{id=9df2bbb0-5c93-11db-be39-91271a7aa7db,  payload=java.util.Properties, correlationId=9df2bbb0-5c93-11db-be39-91271a7aa7db,  correlationGroup=-1, correlationSeq=-1, encoding=UTF-8, exceptionPayload=null, properties={
MULE_ENDPOINT=vm://EMAIL-SERVER-SETTINGS
MULE_CORRELATION_ID=9df2bbb0-5c93-11db-be39-91271a7aa7db
}}
DEBUG 2006-10-15 17:25:01,964 [EmailSettingsUMO.2]  org.mule.routing.outbound.OutboundPassThroughRouter: Message payload: 
{route=ADSL:EMAIL:EMAIL-SERVER-SETTINGS,acct=1234, SMTP=smtpauth.earthlink.net,  POP3=popd.earthlink.net}

Note that the order of UMOs in the log file exactly corresponds to the sequence of keywords in the user input. The following excerpt from the log file illustrates that ProductUMO output is forwarded to the "ADSL" endpoint, since the very first keyword is "ADSL":


DEBUG 2006-10-15 17:25:01,764 [ProductUMO.2] org.mule.routing.outbound.FilteringOutboundRouter:  Message being sent to: vm://ADSL

Also note how the message payload is changed by the last UMO in the chain, EmailSettingsUMO, which resolves email server settings, from

route=ADSL:EMAIL:EMAIL-SERVER-SETTINGS,acct=1234

to


route=ADSL:EMAIL:EMAIL-SERVER-SETTINGS,acct=1234, SMTP=smtpauth.earthlink.net,  POP3=popd.earthlink.net

Now if you try another input, DIALUP:EMAIL:EMAIL-SERVER-SETTINGS,acct=1234, you'll see:


Format: route=address1:...addressN,acct=account,app=applicationroute=DIALUP:EMAIL:EMAIL-SERVER-SETTINGS,acct=1234
DEBUG 2006-10-15 17:33:32,648 [ProductUMO.2] org.mule.routing.outbound.OutboundMessageRouter: Message did not match any routers on: ProductUMO invoking catch all strategy

Note that this input does not match any pattern, since there is no filter defined that includes the "DIALUP" pattern, and, therefore, the catch-all-strategy gets invoked with consequent message flow termination.

Summary

Mule is a flexible framework for implementing message-driven, event-driven systems, and SOA. Its pluggable architecture allows to integrate various--even incompatible--messaging systems using a consistent model. Mule is nicely integrated with popular application containers, including Spring, HiveMind, PicoContainer, and others. Mule's design allows you to implement a number of enterprise integration patterns, with minimum coding effort, in a fairly simple way. This article shows one possible implementation of the Routing Slip pattern using Mule.

Resources

Acknowledgments

The work on this article was encouraged by participation in development projects for Algorithmics, Inc.

Igor Dayen is president of ObjectCentric Solutions, a consulting company that provides Enterprise Integration and XML-based solutions for financial services institutions.


 Feed java.net RSS Feeds