This article describes an architectural pattern, implemented in the Oracle SOA Suite 11g, that is somewhat similar to the Oracle Database 11g Function Result Cache. It introduces a SOA Composite Application in the role of Result Cache. In its most simple form, the result cache is initialized – loaded with values -, used by other SOA applications that need the cached values, refreshed/reset when required and terminated. Through this ‘result cache’ – frequently used and not-so-frequently changed values that are published by (possibly remote, expensive or slow-reacting) web services or adapter services can be made available to local consumers in a simple, cheap and fast manner. We achieve this using the fast native SCA binding used for invoking in-container services exposed by fellow composite applications (that’s for speed) and the BPEL correlation mechanism (that’s for finding the result cache in the first place).
What the actual value of this pattern and implementation are is not yet entirely clear to me. Your feedback is appreciated. What I do know is that this article is also a good demonstration of using correlation and of applying some of the XML manipulation techniques available in BPEL processes.
In a single picture, here is what I created: one or more Composite Instances are initialized; each instance is a cache, identified through its name. Upon initialization, the cache is loaded with values – supposedly important, frequently reused, hard-to-get-by values. Composite applications that are interested in these cached values can retrieve them from the cache, by calling the exposed service and passing the name of the cache (here is where correlation kicks in) and passing the name of the cache entry. The BPEL process looks for the entry and returns the value when found or null and an appropriate message when not found.
Using the terminate operation – and passing the name of the cache to kill – caches can be cleaned up. Using the setValueInCache operation, the user can update an existing cache entry or add a new one.
To get the correlation going, we define a single correlation set – CacheCorrelationSet – that contains a single property: CacheIdentifier. This property indicates the name of the cache instance – there can be more than one. Every request except for the initializeRequest needs to indicate which cache instance it refers to. The initializeRequest itself also specifies a value for the cacheIdentifier and thereby it determines the name of the cache instance that it instantiates.
The property CacheIdentifier is mapped through four different propertyAliases to the four request messages that the four operations published by the BPEL process and exposed by the service composite application receive. This enables the SOA container to extract the target instance identifier from the request message and route the message through to the intended cache instance.
The initial Receive activity is configured to initiate the correlation set and thereby establish the identity of the process instance:
The three onMessage event handlers are each configured with the correlation set – with initiate set to no as the message handled by these handlers should be processed by a running cache instance.
Running the CacheService
The first action we have to perform is initializeCache. This service call instantiates the composite instance, loads it with potentially useful cache entries and establishes the correlation identity of the cache – based on the name passed in to the service with the initializeCache invocation.
When the cache is initialized, we can retrieve values from it:
And update values in it:
When we retrieve the oilPrice value again from the cache, we will retrieve the updated value:
Finally we can terminate the cache which stops the instance and releases the resources held by it:
The instance history is fairly interesting: there is only one BPEL process instance that is accessed from a number of composite instances:
The flow looks like this – note that the flow does not show the actions of onMessage Event handlers:
The CacheManager BPEL process has a global variable globalCache of type cache:cache. This type is defined as follows:
<complexType name="cache"> <sequence> <element name="cacheEntry" type="cache:cacheEntry" maxOccurs="unbounded" minOccurs="0"/> </sequence> </complexType> <complexType name="cacheEntry"> <sequence> <element name="name" type="string" minOccurs="1" maxOccurs="1"/> <element name="value" type="string" minOccurs="0" maxOccurs="1"/> <element name="refreshTime" type="dateTime" minOccurs="0" maxOccurs="1"/> <element name="expiryTime" type="dateTime" minOccurs="0" maxOccurs="1"/> </sequence> </complexType>
It turns out to be not all that easy to find out whether a certain cacheEntry exists in the cache. The XPath expression we need to retrieve a cacheEntry has to be composed dynamically based on the incoming message. As we cannot escape quote characters in assign activities we have to find a workaround. One option is using XSLT and a Transform activity. I chose another somewhat farfetched approached: first I copy the result of querying the global cache for the sought after cache entry to a local variable based on the same cache type. Then I determine the number of cache entry nodes in that local variable. However, since a copy operation in BPEL assign will fail with a nasty exception if the source is empty, I needed another trick. That trick consists of the Oracle BPEL extension, the insertBefore operation: the local cache is initialized with a single dummy cache entry. The assign step’s insertBefore operation adds the query result from the globalCache: this result consists of either one cache entry – if an entry is found – or no entry at all – if there is cache miss. With this operation, it is allowed to have an empty node list to add.
Then we test the local cache for its number of nodes: either one (only the dummy node) or two (dummy and result from the global cache).
In BPEL code this looks as follows:
<scope name="FindAndReturnCacheValue"> <variables> <variable name="getCacheValueOutputVariable" messageType="client:GetCacheValueResponseMessage"/> <variable name="queryString" type="xsd:string"/> <variable name="set" element="cache:cache"/> </variables> <sequence> <assign name="setQueryString"> <copy> <from variable="getCacheValueInputVariable" part="payload" query="/cache:cacheEntryRequest/cache:name"/> <to variable="queryString"/> </copy> <bpelx:append> <bpelx:from expression="'dummy'"/> <bpelx:to variable="set" query="/cache:cache/cache:cacheEntry/cache:name"/> </bpelx:append> <bpelx:insertBefore> <bpelx:from variable="globalCache" query="/cache:cache/cache:cacheEntry[cache:name=bpws:getVariableData('queryString')]"/> <bpelx:to variable="set" query="/cache:cache/cache:cacheEntry"/> </bpelx:insertBefore> </assign> <switch name="CacheHitFound"> <case condition="count(bpws:getVariableData('set','/cache:cache/cache:cacheEntry')) - 1 > 0"> <assign name="Assign_ReturnValueAndStatus"> <copy> <from variable="set" query="/cache:cache/cache:cacheEntry/cache:value"/> <to variable="getCacheValueOutputVariable" part="payload" query="/cache:cacheEntryResponse/cache:value"/> </copy>
Like I said: somewhat far fetched. But it seems to work okay.
JDeveloper 11g Application WebServiceResultCache_11g.zip .