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

Mule version

Tools

I am going to use following tools:

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

Cache Scope

General

Cache Scope allow to reused already saved responses for given requests. You may put as many message processors within Cache Scope as you like. As a response is considered payload after last message processor. The main benefit of this scope is saving resources and handling message quicker.

You should have in mind that this scope by default is using in memory caching strategy. This has such drawback that when mule starts caching large payloads it may reach memory limit and throw java heap exception. As a consequence this defaults strategy should be replaced. This can be done by creating object store caching strategy.

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 distinguish consumable and non-consumable payload. The first one is stream. This means that once your read it you can not read it any more. Whereas non-consumable can be read unlimited number of times. Repeatable streams, new in Mule 4.1, are treated as a non-consumable.

Here are simple table that depicts 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 at BPMN diagram posted above. When payload enters Cache Scope it is checked whether it is consumable or non-consumable. In case of a stream (consumable payload) all message processors are executed withing Cache Scope and the response is nowhere saved. For consumable message is generated key using SHA256. This will identify our payload. Then this key is compared with already saved in Object Store. If key already exists we have situation called cache hit and mule generates response. When we missed cache all message processors within Cache Scope are executed and the result is saved to Object Store.

Implementation

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

Get accounts operation retrieving data from external REST service

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

Caching HTTP request

Mock

I think that easiest way to test if external system was called it to debug service. However I decided to create a mock service using SOAP UI. There we are capable to see how many times mocked service was hit. Example project you can find at GitHub.

Below you can see mocked service. We mock there two operations. Below that you can see call that occurred. Before we start testing the list of call 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 version Mule 4.1 default behavior is caching stream by using repeatable streams functionality. This means that stream can be read multiple times. Unlike to previous version 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 advanced configuration. Below is depicted possible culprit.

If Streaming strategy is set to Non repeatable stream Mule will close stream after first read and 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 200 status code with 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 200 status code with JSON body. In the list of calls in SOAP UI I see still one call. It seems to work as expected.

Below step is valid only for Mule less then 4.1 and streaming strategy set to non repeatable stream.

Third call

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

Result:

We received 500 status code with 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 revolution regarding transformations. In earlier version we needed to explicitly transform payload. For example to perform xpath operation sometimes was needed to perform DOM to XML transformation. Another example is transformation JSON to Map to easily access properties. Another commonly used transformation was Object to String. This was used mainly to consume stream and store extracted value in a String.  Now you may forget about it, mule will do it behind the scene for you. I think that this is a good improvement.

Do you remember the default Strategy? Cache Scope works only for non-consumable payload. So let us see what kind of payload do we get from HTTP Request connector. We get ManagedCursorStreamProvider. This is consumable payload. We need to fix it to enable Cache Scope.

Cache that works

As we know why it does not work, we may fix it. In Mule 3.x I would use Object to String to easily consume it or MEL expression message.payloadAs(java.lang.String). However both methods were not available to me. First one because implicit transformations where provided and transformation message processors are no longer available. The second option did not work because some dependency could not be met and mel expression is not available.

Now in Mule 4.1 I use repeatable stream functionality. We may retest our implementation now. It will work for every call that you make.

Conditional caching

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

Cache-Control: no-cache

it instructs that caching mechanism should not be used. In Cache Scope properties under Filter section you can write 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 Cache-Control different than no-cache.

Time To Live (TTL)

Client should be aware for how long resource will be valid. We may inform consumer by specifying 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 header Expires and return it in each response. In order to enable TTL follow these steps:

  • Add ObjectStore module to mule project
  • Click at Caching Scope and select plus sign next to Reference to a strategy
  • In General Tab:
    • check Object Store
    • provide Alias like Cache_Object_Store
    • uncheck Persistent
    • in Entry ttl provide how many seconds do you want consider response as a valid one
Custom Caching Strategy

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

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

In line 7 we add two minutes to current date and time and then we format it to HTTP date timestamp. Go back to HTTP listener and within add following element

<br />
&lt;http:headers&gt;&lt;![CDATA[#[output applicaton/java --- vars.outboundHeaders ]]]&gt;&lt;/http:headers&gt;<br />

Here is the finally flow:

Source Code

Code is available at GitHub.

Cache or not to cache
Tagged on:             

7 thoughts on “Cache or not to cache

  • 10/03/2018 at 13:44
    Permalink

    Great article Patryk with a good walk through and explanation! Indeed, MEL is not available anymore in Mule4 and Data weave becomes the main expression language. Just one note, you might want to look into using wiremock to mock the external service call (it’s simple and made for stubbing HTTP based calls): http://wiremock.org/docs/running-standalone/.

    Reply
  • 15/03/2018 at 20:38
    Permalink

    Hello,

    Great post! Just one correction. Starting with Mule 4.1 cache scope does cache stream contents. Repeatable streaming is leveraged by this component. Furthermore, it also works with auto-paging operations such as sfdc:query or db:select.

    Thanks!

    Reply
    • 15/03/2018 at 21:34
      Permalink

      Hi,
      I appreciate your comment. I test it now using Mule 4.1 runtime and you are right stream content is cached by default. If you specify Streaming Strategy to Non repeatable stream content won’t be cached.
      Patryk

      Reply
  • 22/03/2018 at 22:18
    Permalink

    Thank you so much for the great article, it was fluent and to the point. Cheers.

    Reply
  • 16/04/2018 at 04:34
    Permalink

    Thanks for the great article.
    When I imported your project, I see below error on os:private-object-store element.
    How do I resolve this?

    Element: private-object-store is not allowed to be child of element Caching Strategy

    Thanks.

    Reply
    • 23/04/2018 at 16:10
      Permalink

      Hi, thanks for the comment. I have just arrived from holidays. I will take a look and response you promptly 🙂

      Reply
    • 26/04/2018 at 08:15
      Permalink

      Hi,
      I found the possible culprit. Could you take a look if in Package Explorer exsists ObjectStore [v1.1.0] dependency? Sometimes newest Anypoint Studio does not load correctly all dependencies present in a pom file. I received the same error when I manually removed ObjectStore module from my project.
      Hopes it helps.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *