Implement a customizable ESB with Java

Integrate heterogeneous applications seamlessly with an enterprise service bus

Consider an enterprise where you have heterogeneous applications, possibly delivered by different teams, that need to interact with each other but have the following constraints:

  • Each application is not necessarily built using the same technology and might not talk to the others using its native invocation mechanism e.g., a J2EE application and .Net application.
  • Preferably, each application shouldn't transform its requests into the format understood by the target application. Plus, the enterprise has many applications that use the target application.
  • Service components should use an invocation or request mechanism that is natural to them. For instance, an existing J2EE application may take requests only via Java Message Service (JMS).
  • The enterprise is moving towards an architecture where an application concerns itself only with, one, what it knows and, two, what it should pass as parameters when it wants to obtain the services of another application within the enterprise.

Other constraints might require you to have an infrastructure that enables heterogeneous applications to integrate seamlessly without altering their design. An enterprise service bus (ESB) is one way of realizing such an enterprise integration architecture.

Although each enterprise will likely create its ESB in its own unique way, it is important to keep flexibility in mind when considering the definition of an ESB. There's no fixed approach to building one. The idea is to have a connectivity layer that optimizes interactions between service consumers and service providers, one that can respond to event-, message-, or service-oriented contexts.

This article discusses an approach for building an extensible Java-based ESB that supports the most common ESB functional requirements.

Common ESB requirements

An ESB's common requirements are also its most commonly used features:

  1. Routing: The ESB should provide an efficient and flexible routing mechanism.
  2. Transformation: A service component shouldn't need to know the request format of the target service that it might invoke. Based on the requester and the target, the ESB should be able to apply the appropriate transformation to the request so the target can understand it.
  3. Multiprotocol transport: An ESB implementation that talks only JMS or only Web services is not of much value. It should be extensible enough to support multiple message protocols depending upon enterprise needs.
  4. Security: If needed, the ESB should enforce authentication and authorization for access to different service components.

Figure 1 shows an ESB's main architectural components. It has three broad compartments:

  1. Receiver: An ESB exposes different interfaces for allowing the client applications to send messages to the ESB. For example, a servlet could be receiving the HTTP requests for the ESB. At the same time, you could have an MDB (message-driven bean) listening on a JMS destination where client applications can send messages.
  2. Core: This is the main part of the ESB implementation. It handles routing and transformation, and applies security. Typically, it is composed of an MDB that receives the inbound requests and then, based on the message context, applies the appropriate transformation, routing, and security. Details about routing, transport, transformation, and security information can be specified in a XML document (discussed shortly).
  3. Dispatcher: All the outbound transport handlers come under this part of the ESB. You can plug any arbitrary transport handler (email, fax, FTP, etc.) into the ESB.
Figure 1. Architectural components of ESB. Click on thumbnail to view full-sized image.

All of these ESB parts are glued together by an XML document that lists all the routes on which the ESB operates. The different transport handlers, transformers, and retry policies and their connection to different routes are all wired via this XML document.

ESBConfiguration.xml

The XML listing—ESBConfiguration.xml, which appears below—gives us some idea about an ESB's workings. The main elements of interest in ESBConfiguration.xml are the following:

  1. Beans: This element contains zero or more Bean elements.
  2. Bean: This element basically defines the way we create and configure a Bean class. It has the following attributes:
    • name: Unique name that can be used to refer to this bean.
    • className: Fully qualified name of the bean class.
    Each bean can have zero or more Property elements as children. Each Property element has an attribute name that identifies it and a child element of type Value that holds the property value. These properties are actually the JavaBeans-style members of the class that can configure the bean class.
  3. RetryPolicies: This element contains zero or more RetryPolicy children.
  4. RetryPolicy: This element defines the retry policy for a given route. It has an attribute name that can be used to refer to it. It has two child elements named MaxRetries and RetryInterval.
  5. Route: The EsbConfiguration root element can contain zero or more child elements of this type. It basically represents a route for the ESB. It has the following attributes:
    • name: Unique name that can be used to refer to this route.
    • retryPolicyRef: Reference to the retry policy. It should match the RetryPolicy element's name attribute.
    • transformerRef: Reference to a bean that represents the transformer. It should match the Bean element's name attribute.
    The Route element can have one or more child elements of type TransportHandlerRef. This child basically points to a bean that represents an appropriate transport handler that should be used for this route, and the public method name of that bean that should be invoked to send the message. Optionally, the Route element can have one DeadLetterDestination child that points to another route representing a dead-letter destination.

A sample XML document, EsbConfiguration.xml, appears below:

 <?xml version="1.0" encoding="UTF-8"?>
<EsbConfiguration xmlns="http://www.bss.org/esb/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <Route name="creditService" retryPolicyRef="100-sec-retry" transformerRef="creditServiceTransform">
      <TransportHandler beanName="creditJMSTransport"/>
      <DeadLetterDestination routeName="DeadLetter"/>
      <AuthConstraint principals="app-1"/>
   </Route>
   <Route name="taxCalculationService" retryPolicyRef="500-sec-retry" transformerRef="taxCalcServiceTransform">
      <TransportHandler beanName="taxCalcWS"/>
      <DeadLetterDestination routeName="DeadLetter"/>
      <AuthConstraint principals="app-2"/>
   </Route>
   <Route name="RedeliveryRequest" retryPolicyRef="500-sec-retry">
      <TransportHandler beanName="redeliveryRequestJMSTransport"/>
      <DeadLetterDestination routeName="DeadLetter"/>
   </Route>
   <Route name="DeadLetter" retryPolicyRef="500-sec-retry">
      <TransportHandler beanName="deadLetterJMSTransport"/>
   </Route>
   <Route name="Redelivery" retryPolicyRef="500-sec-retry">
      <TransportHandler beanName="redeliveryJMSTransport"/>
      <DeadLetterDestination routeName="DeadLetter"/>
   </Route>
   <Route name="Error" retryPolicyRef="500-sec-retry">
      <TransportHandler beanName="errorJMSTransport"/>
   </Route>
   <Beans>
      <!-- Transport handlers for the service components. -->
      <Bean name="creditJMSTransport" className="org.bss.esb.transport.jms.JmsHandler">
         <Property name="ConnectionFactory" type="java.lang.String">
            <Value>qcf-1</Value>
         </Property>
         <Property name="Destination" type="java.lang.String">
            <Value>myCreditQueue</Value>
         </Property>
      </Bean>
      <Bean name="taxCalcWS" className="org.bss.esb.transport.webservice.WebServiceHandler">
         <Property name="WsdlUrl" type="java.lang.String">
            <Value>http://www.tax.com/calc</Value>
         </Property>
      </Bean>
      <!-- Transformer beans for the service components -->
      <Bean name="creditServiceTransform" className="org.bss.esb.transform.XSLTransform">
         <Property name="XslUrl" type="java.lang.String">
            <Value>file:///C:/temp/esb/transform/xsl/credit.xsl</Value>
         </Property>
      </Bean>
      <Bean name="taxCalcServiceTransform" className="org.bss.esb.transform.CustomTransform">
         <Property name="ConfigFileUrl" type="java.lang.String">
            <Value>file:///C:/temp/esb/transform/custom/configManager.properties</Value>
         </Property>
      </Bean>
      <!--  Transport handlers for the system queues -->
      <Bean name="redeliveryJMSTransport" className="org.bss.esb.transport.jms.JmsHandler">
         <Property name="ConnectionFactory" type="java.lang.String">
            <Value>qcf-1</Value>
         </Property>
         <Property name="Destination" type="java.lang.String">
            <Value>Redelivery.Queue</Value>
         </Property>
      </Bean>
      <Bean name="deadLetterJMSTransport" className="org.bss.esb.transport.jms.JmsHandler">
         <Property name="ConnectionFactory" type="java.lang.String">
            <Value>qcf-1</Value>
         </Property>
         <Property name="Destination" type="java.lang.String">
            <Value>System.DL.Queue</Value>
         </Property>
      </Bean>
      <Bean name="errorJMSTransport" className="org.bss.esb.transport.jms.JmsHandler">
         <Property name="ConnectionFactory" type="java.lang.String">
            <Value>qcf-1</Value>
         </Property>
         <Property name="Destination" type="java.lang.String">
            <Value>System.Error.Queue</Value>
         </Property>
      </Bean>
      <Bean name="redeliveryRequestJMSTransport" className="org.bss.esb.transport.jms.EsbRedeliveryHandler">
         <Property name="ConnectionFactory" type="java.lang.String">
            <Value>qcf-1</Value>
         </Property>
         <Property name="Destination" type="java.lang.String">
            <Value>Redelivery.Request.Topic</Value>
         </Property>
      </Bean>
   </Beans>
   <!-- Defines the retry policies that can be used by various route definitions. -->
   <RetryPolicies>
      <RetryPolicy name="100-sec-retry">
         <MaxRetries>10</MaxRetries>
         <RetryInterval>100</RetryInterval>
      </RetryPolicy>
      <RetryPolicy name="500-sec-retry">
         <MaxRetries>10</MaxRetries>
         <RetryInterval>500</RetryInterval>
      </RetryPolicy>
   </RetryPolicies>
</EsbConfiguration>

ESB behavior

The ESBConfiguration.xml document dictates our ESB's behavior. The EsbRouter MDB loads this XML from a location specified in its deployment descriptor. The information it contains is then organized into a datastructure depicted in Figure 2 below.

Figure 2. In-memory configuration data structure. Click on thumbnail to view full-sized image.

The EsbRouter uses this information (via EsbConfigManager) to decipher the appropriate route, the transformation, if any, to be applied, the security authorization check, etc. The important point to note is the way the Dependency Injection technique, along with inheritance, has been used to decouple various functions (such as multiprotocol message transport and message transformation) of the ESB, thus allowing it to be highly extensible and customizable.

As the class diagram shows, two important interfaces are in the ESB design: TransformHandler and TransportHandler. They allow you to write a specific transformation and transport implementation for routed messages. These implementation classes can then be wired with the routes via Bean elements in EsbConfiguration. For example, in the sample EsbConfiguration.xml document, the following bean definition specifies the transport handler:

   <Bean name="creditJMSTransport" className="com.foo.esb.transport.jms.JmsHandler">
         <Property name="ConnectionFactory" type="java.lang.String">
            <Value>myQCF</Value>
         </Property>
         <Property name="Destination" type="java.lang.String">
            <Value>myCreditQueue</Value>
         </Property>
      </Bean>

This transport handler can then be referred to in a Route node by inserting a TransportHandler child to it like this:

 <TransportHandler beanName="creditJMSTransport"/>

Note
The approach described in this article uses Java interfaces for defining the transport and transform handlers. Thus, any new handler would have to implement the required interface, which might seem intrusive. You can easily modify the EsbConfigManager to use Dependency Injection for calling any arbitrary method of an implementation class, thus eliminating the need to implement an interface. But since the EsbRouter always passes a javax.jms.Message instance, your handler implementation class must use the type javax.jms.Message anyway.
1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more