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

To Cache or Not to Cache?

DZone's Guide to

To Cache or Not to Cache?

If Shakespeare had been an integration developer, that's probably what he would have said. But, anyway, read on to see how an actual dev tackles this issue.

· 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.

For high-workload applications, it is important to manage resources efficiently. One of the tricks that can save resource usage is caching. In RESTful services, this technique is used in GET methods. However, it may be used in other cases when the operation for getting particular resources can be reused. In this blog post, I will extend our previously designed REST service by adding caching to two operations. Mule uses a Cache Scope component. Apart from describing it, I will show you possible obstacles and how to handle them.

Tools

I am going to use the following tools:

  • Anypoint Studio to design services offline.
  • SoapUI to mock external services.
  • Postman to perform tests.

Cache Scope

General

Cache Scope allows us to reuse already saved responses for given requests. You may put as many message processors within the Cache Scope as you like, as a response is considered a payload after the last message processor. The main benefit of this scope is saving resources and handling messages quicker. You should have in mind that this scope by default is using an in-memory caching strategy. This has such a drawback that when Mule starts caching large payloads it may reach a memory limit and throw a Java heap exception. As a consequence, this default strategy should be replaced. This can be done by creating an object store caching strategy.

To Cache or Not to Cache, This Is the Question!


It may be tricky at first but not every payload is cached. You may even not notice it at first. Mule distinguishes consumable and non-consumable payloads. The first one is a stream. This means that once you read it you cannot read it anymore. Whereas non-consumable payloads can be read an unlimited number of times. Repeatable streams, new in Mule 4.1, are treated as a non-consumable. Here is a simple table that depicts the difference between payload types:
Consumable Non-consumable
InputStream  jString
BufferInputStream  org.mule.transport.NullPayload
ManagedCursorStreamProvider Byte[]

Strategy

Default Caching Strategy

Caching Scope behavior has been depicted in the BPMN diagram posted above. When a payload enters the Cache Scope it is checked whether it is consumable or non-consumable. In case of a stream (consumable payload), all message processors are executed within the Cache Scope and the response is saved nowhere. For consumable messages, a key is generated using SHA256. This will identify our payload. Then this key is compared with what we've already saved in the Object Store. If the key already exists, we have a situation called cachehit and Mule generates a response. When we missed cache, all message processors within the Cache Scope are executed and the result is saved to the Object Store.

Implementation

Below you can find the flow implementing a GET method for the /accounts resource. During that call, we make a hit to the external RESTful service and, after that, we returned slightly transformed data. As agreed, we decided to cache the response from a MongoDB database.

Get accounts operation retrieving data from external REST service

This is fairly simple because we need to wrap an HTTP Request connector in the Cache Scope. To do it, we need to right click on Request and from the drop-down select Wrap in... and then Cache. Here is the result:

Caching HTTP request

Mock

I think that the easiest way to test if an external system was called is to debug the service. However, I decided to create a mock service using SOAPUI. With this tool, we are able to see how many times a mocked service was hit. You can find an example project you can find at GitHub. Below you can see the mocked service. Here, we are mocking two operations. Below that, you can see the call that occurred. Before we start testing, the list of calls should be empty.

Mocked service in SOAP UI

It Works ... but Wait, it Doesn't Work

As everything is in place it is time to test it.

Repeatable Stream


For Mule 4.1, the default behavior is to cache a stream by using the  repeatablestreams functionality. This means that the stream can be read multiple times. In the previous versions this was not possible, and once you read the stream it was inaccessible. If it happens that your content is not cached you need to take a look at the advanced configurations. Below  a possible culprit is depicted. If Streaming strategy is set to Non repeatable stream, then Mule will close the stream after the first read and the Cache Scope will not work. In order to fix it you may set this value to any other value even none.

First call

GET /api/accounts HTTP/1.1
Host: localhost:8091

Result: We received a 200 status code with a JSON body. In the list of calls in SOAP UI, I see one new line.

Second call

GET /api/accounts HTTP/1.1
Host: localhost:8091

Result: We received a 200 status code with a JSON body. In the list of calls in SOAP UI, I still see one call. It seems to work as expected. The below step is valid only for Mule versions older than 4.1 and with a streaming strategy that's set to a non-repeatable stream.

Third call

GET /api/accounts HTTP/1.1
Host: localhost:8091

Result: We received a 500 status code with the following message: Cannot open a new cursor on a closed stream. So our service's cache is not working properly. But why?

Implicit Transformations


Mule 4 brought a revolution regarding transformations. In earlier versions, we needed to explicitly transform payloads. For example, to perform xpath operations, sometimes we needed to perform a  DOM to XML transformation. Another example is the transformation of  JSON to Map to easily access properties. Another commonly used transformation was Object to String. This was used mainly to consume streams and store extracted values in a String.  Now, you can forget about that, Mule will do it behind the scenes for you! I think that this is a good improvement. Do you remember the default Strategy? Cache Scope works only for non-consumable payloads. So let us see what kind of payload we get from an HTTP Request connector. We get  ManagedCursorStreamProvider. This is a consumable payload. We need to fix it to enable Cache Scope.

Cache That Works

Since we know why it does not work, we can fix it. In Mule 3.x, I would use Object to String to easily consume it or the MEL expression message.payloadAs(java.lang.String). However, both methods are no longer available to me. The first one because implicit transformations were provided and transformation message processors are no longer available. The second option did not work because some dependency could not be met and the mel expression is not available. Now, in Mule 4.1, I can use the repeatable stream functionality. We may retest our implementation now. It will work for every call that you make.

Conditional Caching

We should allow clients to decide whether they want to cache or not. There is a dedicated header for that called Cache-Control. It used to specify and control the caching mechanism in both requests and responses. When a client sends

Cache-Control: no-cache

it instructs the system that a caching mechanism should not be used. In Cache Scope properties, under the Filter section, you can write the condition that message needs to fulfill in order to be cached, like below:

#[attributes.headers.'cache-control' != 'no-cache']

Using this condition, we only cache requests that have a Cache-Control different than no-cache.

Time to Live (TTL)

The client should be aware of how long a resource will be valid. We may inform the consumer by specifying a header like:

  • Cache-Control: max-age=seconds containing number of seconds before resource is considered stale
  • Expires:containing date time value

Examples:

Cache-Control: max-age=120
Expires: Web, 12 Oct 2015 07:28:00 GMT

We need to do two things. First, we need to configure Caching Scope to expire messages and create the header Expires and return it in each response. In order to enable TTL, follow these steps:

  • Add the ObjectStore module to your Mule project.
  • Click on Caching Scope and select the plus sign next to Reference to a strategy.
  • In the General tab:
    • Check Object Store.
    • Provide an Alias like Cache_Object_Store.
    • Uncheck Persistent.
    • In Entry ttl, provide how many seconds you want to consider a response as a valid.

Custom Caching Strategy

A strategy is in place. We need to add a new Transformer to the Cache Scope before executing the script. As cached is the only payload, we need to compose a payload with metadata containing expires headers. Paste following code

%dw 2.0
output application/json
---
{
 Metadata: {
 Expires: (now() + |PT2M|) as String {format: "EEE, dd MMM yyyy HH:mm:ss z"}
 },
 Content: payload
}

In line 7, we add two minutes to the current date and time and then we format it to an HTTP date timestamp. Go back to HTTPlistener and, within it, add the following element:

<http:headers><![CDATA[#[output applicaton/java --- vars.outboundHeaders ]]]></http:headers>

Here is the finally flow:

Source Code

The code is available on GitHub.

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:
mule 4 ,cache ,mulesoft ,integration ,caching

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}