I wrote this article for the monthly Flextras newsletter, and thought I'd share it here.

I was working with a client recently and we had some performance problems while accessing remote data from within our application. The user interface, built with Flex, was accessing our ColdFusion server, and ColdFusion would relay requests onto another remote system using SOAP web services. The web service data didn't need constant updates, so we decided to create a caching system for the data to improve performance. I thought the concepts behind that would make an interesting topic for this month's newsletter.

What Type of Data to Store?

We decided it was best to create a generic system to cache any type of data. The system should handle any type of data we wanted to throw at it. To accomplish this, I created two classes a CacheManager class and CacheData class. The CacheData class is intended to be a generic object used by the CacheManager. Here is a diagram of the class:

The CacheData class is used by CacheManager to store the cached data. It has three properties: the data to store and two dates. The first date is, dateCached, which keeps track of the date and time that the data was first cached. The second element is dateLastAccessed, which keeps track of the date that the data was last accessed. The data property should be protected, or private, or something similar depending upon your language of choice. The two date properties should be publicly accessible.

The class has two methods: get() and set(). The get method returns the data and should also update the last accessed date value to the current date and time. The set method will update the internal data property, as well reset the two dates. In an attempt to keep this as platform agnostic as possible; this would be pseudo code for the class:

view plain print about
1package{ class CacheData{
2 // this value stores the data
3 protected var data : *;
4 // this value stores the date that the data was initially stored
5 public var dateCached:Date
6 // this value stores the date that the value was last accessed
7 public var dateLastAccessed:Date
8 // this method can be used to retrieve the data
9 public function get ():*{
10 dateLastAccessed = getCurrentDate();
11 return data;
12 }
13 // this method can be used to set the data
14 public function set(value:*):void{
15 data = value;
16 dateCached = getCurrentDate();
17 dateLastAccessed = getCurrentDate();
18} } }

This is a very simple class, not much more than a glorified value object.

The CacheManager Class

The CacheManager class is a bit more complicated. This is your application's window into the cached data.

This is the class diagram for the CacheManager:

The CacheManager has two properties; minutesCached and cachedData. The minutesCached property is an internal value will be used to determine when data should be flushed and deleted. The cachedData is also an internal value, but it is a bit more complicated. I created that as an associative array of associative arrays. In Flex, an associative Array would be a Dictionary; in ColdFusion that would be a Struct. The first key of the associative array is the type of data you want to store. I call this the cacheKey. This may be "products" or "orders" or something similar, depending on the data you want to store. The second associative array's key is the dataKey. Examples of this may be "ProductID1" or "Order13" or anything that can be used to uniquely identify the type of data, such as its database's Primary Key.

The combination of the dataKey and the cacheKey is used to retrieve the data. The use of the two key structure allows a single instance of the CacheManager to be used to cache multiple types of data. There are also two methods as part of the cacheManager: store() and retrieve(). The store method accepts three arguments: the data, the cacheKey, and the dataKey. It will create a new object of CacheData object and call the CacheData.set method to store the data. It is the simpler of the two methods.

The retrieve method is where the magic happens. It first check's to verify that the data exists. Then it checks to make sure that the data is still relevant based on the current date and the date that the data was last accessed. If the data is too old; then it can be deleted. Otherwise it can be returned. This is a pseudo code class:

view plain print about
1package{ class CacheManager{
2 // this value specifies how many minutes the data should be cached
3 protected var minutesCached : int = 1440;
4 // this value contains the cached data; using a two key structure. A CacheKey and a DataKey.
5 protected var cachedData:Dictionary = new Dictionary();
6 // this method can be used to retrieve the data
7 public function store (data:*, cacheKey:String,dataKey:String):void{
8 if(!cachedData[cacheKey]){
9 cachedData[cacheKey] = new Dictionary();
10 }
11 cachedData[cacheKey][dataKey] = new CacheData();
12 cachedData[cacheKey][dataKey].set(data);
13 return data;
14 }
15 // this method can be used to set the data
16 public function retrieve(cacheKey:String,dataKey:String):*{
17 var cacheObject = cachedData[cacheKey][dataKey]
18 if(!cacheObject){ return; }
19 var dateToClearCache :Date = getCurrentDate() - minutesCached
20 if(cacheObject.dateLastAccessed <= dateToClearCache){
21 cachedData[cacheKey][dataKey] = null; return;
22 } Return cacheObject;
23} } }

Although it is beyond the scope of this article, you could also create a method to automatically clear old cache data. This is probably more memory efficient than waiting until the data is retrieved to clear it out.

Putting it All Together

Now that we have the code behind our CacheManager, the last step is to put that to use. First, create an instance of it; presumably in some globally accessible variable.

view plain print about
1public var cacheManager :CacheManager = new CacheManager();

Let's say you just loaded a bunch of products from some remote source. You'd loop over them and store them in the CacheManager instance:

view plain print about
1For each product in productArray
2 cacheManager.store (product, 'products',product.id):
3End For Loop

In this code, we use the cacheKey named products and use a product ID for the dataKey. I once read something that said you when you file something; you want to make sure to file it in the way that you can find it when you need it. The dataKey for your cache should follow the same suit. When you need to retrieve the data, you want it to be filed under some name that you can use to retrieve the data later.

Retrieve the data from the cache when you need it, like this:

view plain print about
1var product : Product = cacheManager.retrieve('products',product.id);

Be sure to check that you actually got a product back from the cache before you try to use it:

view plain print about
2 // do something to retrieve the product data from source
3 // or make sure your code accommodates for a non-existent product
5// process product

Final Thoughts

I hope you found this article interesting. I did my best to make this as conceptual as possible, so you may have to modify the code in order to work in the language of your choice.

I have two other mini-announcements that I added to the recent Flextras Newsletter. First, the code that never became the Flextras Flex Spreadsheet Component has been open sourced by the original author. It is a very impressive piece of technology. Second, if you're in the mood for interesting weird music; I recorded a full album worth of songs for February Album Writing Month. It is a nice little diversion from programming at times.