The WSO2 Enterprise Integrator Cache Mediator is a very powerful Mediator, that caches service responses by hashing the request messages. When a message enters a message flow, the Cache Mediator checks whether the incoming message is ‘similar’ to a previous message that was received within a specified period of time. The similarity is calculated by evaluating the hash value of the entire incoming message (that is, the Caching Key is identical to the entire message body, and can contain or ignore message headers by configuration). If a ‘similar’ message was identified before, the Cache Mediator fetches the cached response, and sends it back; else the normal flow resumes, presumably calling the backend and subsequently storing the response in the cache.
There are cases where we would prefer that the Caching Key is not identical to the whole incoming message; for example, when a message:
- Contains one or more fields that change for every request
- They are not relevant for response caching
A typical scenario is a timestamp field (which is changing every time and is typically not relevant for the execution of the service logic). Ideally the timestamp should be in the message headers, where the Cache Mediator can be configured to ignore it. But if it isn’t (perhaps because consumers are not able to insert message headers), the Cache Mediator’s default behaviour will include the timestamp in cache lookups, and it will produce a cache miss every time, unnecessarily wasting CPU power and filling the entire cache with entries that will never be used! CPU and memory waste cannot be ignored: in fact, we are using caching exactly because we want maximum performance, but, conversely, we achieve a slower and memory-consuming service.
In these scenarios, we don’t need to throw away the Cache Mediator and re-invent the wheel. We just wish more granular control of the Hashing Key, for example excluding unnecessary fields like the timestamp. A message transformation (XSLT) that includes only the relevant information, and excludes the unwanted one, allows us to invoke the standard Cache Mediator and to achieve our performance goals.
Let’s illustrate the technique with an example. Consider a service that converts ZipCodes to City Information (for example, to the input ‘CH-3000’ corresponds a ‘Bern’, ‘Switzerland’ output). Here is the input message:
2018-08-01 10:00:00 <!--always changing and irrelevant-->
CH-3000 <!--relevant information for service logic-->
The Caching Key in this case can be the XPath expression “//ZipCode”, while the TimeStamp should be ignored. We proceed to construct a proxy service that:
- Saves the message Body in a Property (“originalPayload”),
- Then, replaces the Body with an XSLT-calculated Caching Key (“//ZipCode”),
- Finally, calls the Cache Mediator,
- If there is a cache hit, the Cache Mediator automatically and immediately responds,
- If there is a cache miss:
- The proxy restores the original message Body,
- Invokes the Backend,
- Caches the response and sends it back.
The highlighted payloadFactory contains the simple transformation that provides granular control of the Caching Key:
<?xml version="1.0" encoding="UTF-8"?>
<proxy name="CachedDate" startOnLoad="true" transports="http https" http://ws.apache.org/ns/synapse">http://ws.apache.org/ns/synapse">
<enrich description="Save originalPayload">
<source clone="false" type="body"/>
<target property="originalPayload" type="property"/>
<payloadFactory description="CreateCacheHashingKey" media-type="xml">
<arg evaluator="xml" expression="//ZipCode"/>
<cache collector="false" maxMessageSize="2000" timeout="30">
<enrich description="Restore Payload">
<source clone="false" property="originalPayload" type="property"/>
<log category="ERROR" level="full"/>
The above proxy fulfils our needs.
Finally, the same Caching Key manipulation technique can be used in opposite scenarios as well, when we need for example to enrich the Caching Key with meta-data that is not part of the original message (body or header), such as ESB properties (axis2, transport, synapse etc.), or any values calculated, or fetched from memory or from an external source. In this way, we have a mechanism to artificially partition the Cache Mediator’s cache in any way that fits our business and security needs.