Friday, October 25, 2013

Simple Mule 3.4 Component Binding Complete Example

We have been looking at Mule to help with our routing needs as part of our ongoing decomposition projects.  I have experience with other ESBs and prefer using some programmatic control, so flows can be easily componentized and then declaratively wired together.  I believe this approach balances the programmatic and declarative control and uses the strengths of each to promote flexibility and maintainability.

Mule Component Bindings seemed to be the solution I was looking for, so I started to focus my research in that area.  Unfortunately, the documentation available on the component binding approach was dated and didn't work out-of-the-box with the Mule 3.4 version, so I took some time to create a simple example to demonstrate the functionality end-to-end in a small footprint.

This Mule Component Binding Example, uses both Mule declarations and a Java class to direct the flow to the components declared in the BindTestObjectProof.xml file. To follow this example, the reader must have some basic understanding of Mule, Java, HTTP and JMS.  The example's encapsulated flow, functionality, structure, comments and component naming is purposefully simplistic and verbose to facilitate understanding.


The diagram above shows the example flow that consists of the following steps:
  • Step 1: A client makes an HTTP request to http://localhost:8081, initiating the flow.  A string is appended to the message payload and the request is directed to the TestInitiator:processRequest method called through the Mule's Default Behavior, passing in the augmented message payload.
  • Step 2: The TestInitiator:processRequest method creates a TestDao object, adding the passed-in request data to each field.  Then the TestObjectServiceInterface:repeatNameString method is called with the TestDao object, which sends a message to the "vm.ObjectDirectService" inbound-endpoint declared in the flow.
  • Step 3: The "vm.ObjectDirectService" endpoint passes the message to the TestObjectDirectService:repeatNameString method with the payload as an argument.  A String is appended to each of the TestDao fields and the message response is sent with the payload returned by the Java method.
  • Step 4: In the TestInitiator:processRequest method, the  TestObjectServiceInterface:repeatNameString method returns with the augmented TestDao object.
  • Step 5: The TestInitiator:processRequest method then calls the TestObjectServiceInterface:repeatNameJms method with the TestDao object, which sends a message to the "vm.ObjectJmsService" inbound-endpoint declared in the flow.
  • Step 6: The "vm.ObjectJmsService" endpoint uses a request-reply construct to synchronize the JMS request/response MEP and posts a message to the JMS outbound-endpoint wired to the "QueueObjectRequest" queue. 
  • Step 7: The JMS message is received by the JMS inbound-endpoint listening on the "QueueObjectRequest" queue
  • Step 8: The message is passed to the TestObjectJmsService:repeatNameJms method with the payload as an argument.  A string is appended to each of the TestDao fields and returned.
  •  Step 9: A JMS response message is generated with the payload returned by the Java method and then sent to the JMS outbound-endpoint wired to the "QueueObjectResponse" queue.
  • Step 10: The JMS response message is received by the JMS inbound-endpoint listening on the "QueueObjectResponse" queue and returned up the flow.
  • Step 11: The message is returned up the flow.
  • Step 12: In the TestInitiator:processRequest method, the  TestObjectServiceInterface:repeatNameJms method returns with the augmented TestDao object and then the TestInitiator:processRequest method also returns the augmented TestDao.  
  • Step 13: The message is returned up the flow, transformed to JSON and sent in an HTTP response to the client.

Running the Example

To share this example, I exported a Mule zip (with source code) and uploaded to my public Google Drive at MuleBindTestProof.zip.


When Running the example in Mule, you should see the following output from the command line
>curl.exe http://localhost:8081/
{"name":"mark/-000-String-JMS","address":"milpitas/-000-String-JMS","description":"testing/-000-String-JMS"}

In the mule logs/console, you should see something like the following (depending on your log level):
**START TestInitiator:processRequest - request=/-000

Return from repeatNameString - result=name=mark/-000-String; address=milpitas/-000-String, description=testing/-000-String

Return from repeatNameJms - result=name=mark/-000-String-JMS; address=milpitas/-000-String-JMS, description=testing/-000-String-JMS

**END TestInitiator:processRequest - result=name=mark/-000-String-JMS; address=milpitas/-000-String-JMS, description=testing/-000-String-JMS


Walk through of the Mule Flow Snippet (BindTestObjectProof.xml)

Declaration to Configure JMS broker used in example. Used above in Steps 7-10
<jms:activemq-connector name="Active_MQ" specification="1.1" brokerURL="vm://localhost" validateConnections="true" doc:name="Active MQ Broker" />


Declaration of the http endpoint, the message sending to the TestInitiator after appending a string and the conversion of the resultant object to JSON after the flow is exected. Described above in Steps 1 and 13 
<flow name="TestInRequest" doc:name="TestInRequest"> 

  <!-- Inbound endpoint used to initiate flow by http call to localhost:8081 --> 
  <http:inbound-endpoint exchange-pattern="request-response" host="localhost" port="8081" doc:name="HTTP Entry Point" />   
  <!-- Route to Java flow Initiator --> 
  <vm:outbound-endpoint path="vm.TestInitiator" exchange-pattern="request-response" doc:name="Call to TestInitiator flow">   
    <!-- append request value with static value before message is sent for demo purposes --> 
    <append-string-transformer message="-000" /> 
  </vm:outbound-endpoint> 

  <!-- Transform message payload to json --> 
  <json:object-to-json-transformer doc:name="Transfor output to JSON" />
</flow>


Declaration of the TestInitiator inbound-endpoint and the TestInitiator component that is bound to the 2 outbound-endpoints through the TestObjectServiceInterface methods. Described above in Steps 2, 4, 5 and 12.
<flow name="TestInitiator" doc:name="TestInitiator"> 

  <!-- Inbound endpoint mapped to caller path above through attribute path="vm.TestInitiator" (vm.* is just a naming     convention for clarity, not a required prefix) --> 
  <vm:inbound-endpoint path="vm.TestInitiator" exchange-pattern="request-response" doc:name="Inbound endpoint for     
    Initiator" /> 

  <!-- Java class that will call the Interfaces Methods that is wired-up through the bindings --> 
  <component class="com.intuit.bind.TestInitiator" doc:name="Java Initiator"> 

    <!-- Bind the outbound endpoint to TestObjectServiceInterface:repeatNameString method which is used by TestInitiator
      (set via setTestObjectServiceInterface) --> 
   <binding interface="com.intuit.bind.TestObjectServiceInterface" method="repeatNameString"> 
     <vm:outbound-endpoint path="vm.ObjectDirectService" exchange-pattern="request-response"/> 
    </binding> 

    <!-- Bind the outbound endpoint to TestObjectServiceInterface:repeatNameJms method which is used by TestInitiator (set 
    via setTestObjectServiceInterface) --> 
    <binding interface="com.intuit.bind.TestObjectServiceInterface" method="repeatNameJms"> 
      <vm:outbound-endpoint path="vm.ObjectJmsService" exchange-pattern="request-response"/> 
     </binding> 
  </component> 
</flow>


Declaration of the vmObjectDirectService inbound-endpoint. Described above in Step 3.
<flow name="ObjectDirectService" doc:name="ObjectDirectService"> 

  <!-- Inbound endpoint mapped through the path attributes called via binding of 
    TestObjectServiceInterface:repeatNameString --> 
  <vm:inbound-endpoint path="vm.ObjectDirectService" exchange-pattern="request-response" 
    doc:name="Inbound endpoint" /> 

  <!-- Call to java class to modify object, but this could be anything (e.g. call to external service) --> 
  <component class="com.intuit.bind.TestObjectDirectService" doc:name="Java" /> 
</flow>


Declaration of the JMS Service "vm.ObjectJmsService" inbound-endpoint and the JMS message orchestration. Described above in Steps 6, 7, 10 and 11.
<flow name="ObjectJmsService" doc:name="ObjectJmsService"> 

  <!-- Inbound endpoint mapped through the path attributes called via binding of 
    TestObjectServiceInterface:repeatNameJms --> 
  <vm:inbound-endpoint path="vm.ObjectJmsService" exchange-pattern="request-response" doc:name="Inbound endpoint" /> 

  <!-- Request-reply construct to synchronize the JMS request/response MEP --> 
  <request-reply> 

    <!-- Outbound endpoint to send JMS request --> 
    <jms:outbound-endpoint connector-ref="Active_MQ" queue="QueueObjectRequest" /> 

    <!-- Inbound endpoint to receive JMS response --> 
    <jms:inbound-endpoint connector-ref="Active_MQ" queue="QueueObjectResponse" /> 

 </request-reply> 
</flow>


Declaration of the JMS inbound-endpoint described above in Steps 8 and 9
<flow name="ObjectJmsEndpoint" doc:name="ObjectJmsEndpoint"> 

  <!-- JMS Inbound endpoint mapped through the queue attributes called via jms:outbound-endpoint --> 
  <jms:inbound-endpoint exchange-pattern="request-response" queue="QueueObjectRequest" 
    connector-ref="Active_MQ" doc:name="JMS Inbound endpoint" /> 

  <!-- Call to java class to modify object, but this could be anything (e.g. call to external service) --> 
  <component class="com.intuit.bind.TestObjectJmsService" doc:name="Java" /> 
</flow>


Walk through the Java Class Snippet (TestInitiator.java)

// instance variable that holds the interface used to invoke the outbound-endpoints
// wired-up declaratively
private TestObjectServiceInterface testObjectService;

  // Method called through the Mule's Default Behavior, passing in the augmented message payload.
  public TestDao processRequest(String request) {

  // create object to send
  TestDao dao=new TestDao("mark" + request, "milpitas" + request, "testing" + request);

  // set object to service that will append "-String" to each variable
  TestDao result = getTestObjectServiceInterface().repeatNameString(dao);

  // set object to service that will append "-JMS" to each variable
  result = getTestObjectServiceInterface().repeatNameJms(result);
  return result;
}
// This mutator is called by Mule to pass in the interface used to invoke the outbound-endpoints
// wired-up declaratively
public void setTestObjectServiceInterface(TestObjectServiceInterface testObjectService) {
  this.testObjectService = testObjectService;
}

public TestObjectServiceInterface getTestObjectServiceInterface() {
  return testObjectService;
}



Conclusion

By using both programmatic and declarative methodologies it enables the creation of reusable components at different levels of granularity, easily re-routable flows and to program using strong types where desired.

We are in the process of performance testing Mule and its routing capabilities.  Depending on the result, we may be using it as a central piece of our service infrastructure going forward.