Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Mule Application Development: Effective Caching

DZone's Guide to

Mule Application Development: Effective Caching

In this walkthrough, you'll learn how to implement caching, a key integration pattern, within Mule flows in a Mule application.

· Integration Zone ·
Free Resource

SnapLogic is the leading self-service enterprise-grade integration platform. Download the 2018 GartnerMagic Quadrant for Enterprise iPaaS or play around on the platform, risk free, for 30 days.

In any integration project, caching is a crucial part of the design. Certain data are very unlikely to change for a period of time. In those situations, we do not want to retrieve the data everytime from a data source, such as a database, file, etc.

Mule Anypoint Platform provides two key caching mechanisms. One is context registry, which basically is a Java HashMap inside the MuleContext object. We can use the registry to store and retrieve data. The other is cache scape, which stays inside a flow or sub-flow. When the system hits the component, it checks its cache. If it has the key, it will return the value. Otherwise, it will invoke the cache scope, which typically will retrieve data from data sources. For more details about the cache component, please refer to Mule documentation here.

  1. How to convert a CSV file to java HashMap using the Mule Dataweave Component
  2. How to use Mule registry to store and retrieve data
  3. How to use caching and validator
  4. How to use Mule context notification
  5. How to invoke Mule flow from Java code 

For the code development, I am using Mule embedded runtime version 3.9.0, and JDK 1.8.144. 

The Requirements

  1. A CSV file contains country code mapping with two columns of country codes. The first column is three-letter-code and the second column is the two-letter-code as the following:

    Three Two
    AUS AU
    USA US
    CAN  CA
    ... ...
  2. The file should be loaded when the Mule Application is started.
  3. The HashMap is stored in memory with the key as the two-letter country code.
  4. The key should be case insensitive.
  5.  If a null or empty string of the key is received, throw an exception.
  6. Example:
    • If a user enters Au, aU, AU, the system should return AUS.
    • If a user enters null or "" (empty string), the system should throw an exception.

Mule Flow Design and Coding

Here are the key design questions we need to answer:

  1. How to load a file and convert the data to a HashMap.
  2. Where to store the HashMap.
  3. How we can trigger the data loading flow when the Mule application is started.

The following diagram depicts the high-level design. The lower part indicates that loading the data, converting the CSV to a Java HashMap, and storing the data in the Mule context registry should be done immediately after the application is started. Users get data (three-letter country code) from a flow which apply Mule Cache Scope.

Image title

Load Data and Convert It to HashMap

The design to answer questions 1 and 2 is as follows:

Load csv file and convert the data to Java HashMap

The source code is as follows:

   <flow name="effective-caching-load-data-Flow">
        <parse-template location="Country_Code_Mapping.csv" doc:name="load coutry code"/>
        <dw:transform-message doc:name="Transform Message" metadata:id="accb6b1c-7213-4b98-a8c3-001972044537">
            <dw:input-payload mimeType="application/csv"/>
            <dw:set-payload><![CDATA[%dw 1.0
%output application/java
---
payload map {
($.Two) : $.Three
 } reduce ($$ ++ $)]]></dw:set-payload>
        </dw:transform-message>
        <scripting:component doc:name="Groovy">
            <scripting:script engine="Groovy"><![CDATA[muleContext.getRegistry().registerObject('countryCodeHashMap', payload)
return payload]]></scripting:script>
        </scripting:component>
        <logger message="#['Country Code Data Loader: ' + payload]" level="INFO" doc:name="Logger"/>
    </flow>

Line 2 is for loading the CSV file using Mule's parse-template component. The CSV file can be anywhere in the project classpath.

Lines 3-10 are for transforming the CSV data to Java HashMap. There are few tricks in the DataWeave code. The first one is using data as the key of the HashMap. The syntax is ($.two) . Another trick is converting a list of HashMap to a single HashMap. That was realized by the reduce function  reduce ($$ ++ $) .

Lines 12-15 is Groovy code to set the HashMap to the Mule context registry:

muleContext.getRegistry().registerObject('countryCodeHashMap', payload)
return payload

Groovy is a very powerful language available in Mule. Mule makes it is very easy to use because the platform passes many variables to the Groovy scripts. The main variables available are:

 muleContext, message, payload flowVars, sessionVars 

Cache Scope Flow

The following diagram shows the caching flow:

Image title

The source code is as follows:

    <flow name="effective_caching-main-Flow">
        <validation:is-not-empty config-ref="Validate_Null_Payload" message="#['Input country code cannot be null']" value="#[payload]" doc:name="Validation"/>
        <ee:cache doc:name="Cache">
            <set-payload value="#[app.registry.get('countryCodeHashMap')[payload.toUpperCase()]]" doc:name="Set Payload"/>
            <logger level="INFO" doc:name="Logger" message="#['Retrieve Country: ' + payload]"/>
        </ee:cache>
                <choice-exception-strategy doc:name="Choice Exception Strategy">
            <catch-exception-strategy 
            when="#[exception.causedBy(java.lang.IllegalArgumentException)]"
            logException="false" 
            doc:name="Catch Invalid data Exception Strategy" >
                <set-payload value="#['Invalid Data: ' + payload]" doc:name="Set Payload"/>
                <set-variable variableName="reason" value="invalid input" doc:name="Variable"/>
                <set-variable variableName="statuscode" value="400" doc:name="Variable"/>
            </catch-exception-strategy>
            <catch-exception-strategy 
            when="#[exception.causedBy(java.lang.Exception)]"
            logException="false" 
            doc:name="Catch Unknow Exception Strategy" >
                <set-payload value="#['Data: ' + payload]" doc:name="Set Payload"/>
                <set-variable variableName="reason" value="invalid input" doc:name="Variable"/>
                <set-variable variableName="statuscode" value="400" doc:name="Variable"/>
            </catch-exception-strategy>
        </choice-exception-strategy>
    </flow>

The validation configuration is very simple as the following:

<validation:config name="Validate_Null_Payload" doc:name="Validation Configuration"/>

The above code satifies the requirement of null or empty payload and using caching to save the time to access mule registry.

So I have shown all the mule flow for loading data and access the data using caching component. The next, I will show how to kick the flow when the Mule application is started.

Invoke a Mule Flow When the Application Starts

The key design here is to write a Java class to listen to the Mule context notification  MuleContextNotification.CONTEXT_STARTED . Once the bean receives the notification, it will invoke the Mule flow for a data load, effective-caching-load-data-Flow. This will satify the requirement of loading data when the Mule application is started. 

Here is the Java source code:

public class FlowInvokerContextListener implements  MuleContextNotificationListener<MuleContextNotification>{
@Inject
    private MuleContext muleContext;
    private String flowName;
private static final Logger logger = LoggerFactory.getLogger(FlowInvokerContextListener.class);

    @Override
    public void onNotification(MuleContextNotification notification){
  if (notification.getAction() == MuleContextNotification.CONTEXT_STARTED) {
  logger.info("OK FlowInvoker Is Working");
  FlowConstruct flowConstruct = muleContext.getRegistry().lookupFlowConstruct(this.flowName); 
        final MuleEvent muleEvent = new DefaultMuleEvent(new DefaultMuleMessage(null, muleContext),
            MessageExchangePattern.REQUEST_RESPONSE, flowConstruct);

          try {
              final MuleEvent resultEvent = ((Flow)flowConstruct).process(muleEvent);
          } catch (MuleException e) {
              logger.error("MuleException", e);
          }
}
}

  public String getFlowName() {
      return flowName;
  }

  public void setFlowName(String flowName) {
      this.flowName = flowName;
  }
}

The above code is self-explanatory. Note that I did not hardcode the flow name, so that this piece of code can be reused. 

To complete the code, we need to create a spring bean as per the following:

<spring:bean id="flowInvoker" name="flowInvoker"
     class="com.ggl.esb.utils.FlowInvokerContextListener"
     scope="singleton">
     <spring:property name="flowName" value="effective-caching-load-data-Flow"/>
</spring:bean>

Conclusions

The Mule application platform provides powerful components, such as DataWeave, caching, validator, context injection, etc. The spring framework incorporated into the platform makes coding much easier.

I have demonstrated the key integration pattern of caching. Together, I have documented the Mule language usages of:

  • Loading a CSV file using the Parse-Template component
  • Tricks using DataWeave to convert CSV to Java HashMap using the reduce function
  • Using the Mule context registry to store and retrieve cached data   
  • Using the Mule Cache Scope to case data from the flow level

With SnapLogic’s integration platform you can save millions of dollars, increase integrator productivity by 5X, and reduce integration time to value by 90%. Sign up for our risk-free 30-day trial!

Topics:
enterprise integration ,integration ,mule ,tutorial ,caching

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}