Nov
25

Dependent Caching is a new feature in ColdFusion 9 that I immediately thought would be really useful. But when it came time to create an actual example for my BFusion talk back in October I was at a loss. I talked with my team at Dealerskins hoping they'd have some good ideas and while we all came up with a few none were fantastic. As MAX 2009 approached I continued to struggle with concepts I thought were doable but just weren't "real world" enough. I wound up creating an example based on a fictional order processing system. This still isn't good enough in my mind, but it works and should be fine for an example in a blog post. With that said, Rob has come up with a better example so I encourage folks to read through his post on this topic as well. But don't leave just yet! Read through this post first.

Exactly what is dependent caching in ColdFusion 9? It's a way to store cached pages or page fragments based on the value of a variable or list of variables. Consider the following pseudo code:

<!--- Dashboard access levels: superuser, administrator, analyst, editor --->
<!--- Set the dashboard level based on some lookup --->

[access-level-lookup-code-here]

if superuser
    dashboardLevel = "superuser"
if administrator
    dashboardLevel = "administrator"
end if

<cfcache action="cache" id="dashboard-cache" dependsOn="dashboardLevel" timespan="#CreateTimeSpan(0,0,30,0)#">
    [some-crazy-sick-awesome-complicated-dashboard-display]
</cfcache>

This example shows pseudo code that could be used to display and cache a dashboard interface for different levels of users. Perhaps the dashboard includes modules that run complicated queries against a database and then displays an intricate HTML/CSS interface to represent the data. There are several different types of dashboards depending on the access level of the authenticated user. Superusers for example might see a dashboard that gives them insight into the health of the application. Administrators might see reporting data on how the organization is functioning with respect to key performance indicators (KPIs). Instead of having the code on the page run every time the user visits the dashboard, you simply cache the page for each user that has the specific access level. In this case there would be four cached pages each with a timespan of thirty minutes. The first user to access the dashboard in each access level would cause the page fragment to be cached. Subsequent users in each access level would see the page quickly since it would be pulled from cache.

Let's take a look at another example, this one using real code. Again, I don't think this is an awesome example, but it does demonstrate dependent caching.

Imagine an order processing system that performs heavy processing each time a new order is submitted into the system and queued via a Web interface. Order fulfillment specialists access the order processing system which retrieves new orders from a database. When no new orders exist, information about previous orders are displayed. When new orders exist, they are processed with some magical, super heavy code that takes a while to run. This code needs to be cached to help the page load faster on later visits. Here's the watered-down code that powers the system.

<!--- Get the orders for the database. --->
<cfquery name="getOrders" datasource="cfartgallery">
    SELECT ORDERID
    FROM ORDERS
    ORDER BY ORDERDATE DESC
</cfquery>

<!--- Cache a heavy process that depends on the number of records in the Orders table. --->
<cfcache action="cache" id="wt-9-cache" dependsOn="getOrders.RecordCount" timespan="#CreateTimeSpan(0,0,30,0)#">
    <cfoutput>Some big heavy process that does something with new orders...
    <cfset sleep(1000)>
    <br />
    Number of orders #getOrders.RecordCount# on #Now()#
    </cfoutput>
</cfcache>

The first thing the processing system does is retrieve orders from the database. The code here is using the built in Apache Derby cfartgallery database. After retrieving the orders a <cfcache> block is started with the dependsOn attribute added. This particular code is dependent on the number of records in the ORDERS table. If the number of records has changed (new orders) the page performs tasks that process the order. Next, to simulate the load of a heavy page we ask ColdFusion to sleep for one second. Finally, we output the number of orders and the current server date/time of the order.

To see this code in action, run it on your ColdFusion 9 server. You don't need to create a database or even a datasource since we're using the cfartgallery database.

Step 1:
Run this page in a browser to cache the page fragment based on the number of current records. If your database is still in its default state you should have 23 records in the ORDERS table. Here's the output from my browser.

After little more than a second, the page renders and you see the number of orders along with the current date/time of the server. If you refresh the page it should appear to run a little quicker since the page fragment is pulled from cache and the sleep() code isn't executed. At this point we have a cached fragment that is dependent on the number of orders equaling 23.

Step 2:
For step 2, create a new ColdFusion template named whatever you want (the downloaded code uses the name wt-9-update.cfm). Ensure the following code is in the template.

<cfquery name="addOrder" datasource="cfartgallery">
    INSERT INTO ORDERS (TAX, TOTAL, ORDERDATE, ORDERSTATUSID, CUSTOMERFIRSTNAME, CUSTOMERLASTNAME, ADDRESS, CITY, STATE, POSTALCODE, PHONE)
    VALUES (10, 100, #CreateODBCDateTime(Now())#, 1, 'Aaron', 'West', '2009 ColdFusion Ave', 'Nashville', 'TN', '37203', '415-555-9836')
</cfquery>

<!---<cfquery name="deleteOrder" datasource="cfartgallery">
    delete
    from ORDERS
    where ORDERID = 24
</cfquery>--->

Run this new template in your browser. The insert query will run and create a new record in the art gallery ORDERS table.

Step 3:
Now that we've added a new record to the ORDERS table, rerun the original template. You should see results similar to the following.

Since a new order has been placed in the system and the record count of the getOrders query has changed, ColdFusion will process the order and cache the results of the page independently from the previous version of the page. This is a pretty significant event given the URL of the page didn't change. Why? Well, to have different versions of a page cache independently from one another the obvious thing to do is to pass in URL parameters and instruct ColdFusion to cache different versions using useQueryString="true" in the <cfcache> tag. But dependent caching with the dependsOn attribute is another way to accomplish the same task. I actually think dependsOn is a little more flexible and powerful than useQueryString, but both have their place.

Step 4:
To see the effect of the cached orders, comment out the insert query you created in step 2 and uncomment the deleteOrders query. Rerun your second template. Now go back to the first template and run it again. You should be back to the original number of orders (23). In fact, the date/time displayed on the page should be the same as the date/time you observed in step 1. Here's the output from my browser.

This time the page executed the order processing didn't occur. Instead, the basic order information was retrieved from template cache and the page loaded faster. Obviously this example is a bit of a stretch. How many order processing systems would process an order based on a record count value from a database. None, probably. But, the first example as well as this orders example show what's possible with dependent caching in ColdFusion 9.

What sort of use cases can you come up with for dependent caching? If you have any clever ideas submit a comment below; I'd love to read your thoughts.

Click here to download the code mentioned in this post.

Aaron West's Gravatar
About this post:

This entry was posted by Aaron West on November 25, 2009 at 10:03 PM. It was filed in the following categories: ColdFusion. It has been viewed 13470 times and has 3 comments.

13 related blog entries

3 Responses to 14 Days of ColdFusion 9 Caching: Day 9 - Dependent Caching

  1. PJ

    So if I'm understanding this correctly; I have one site where the data updates only occasionally through the day. The pages with queries are very heavy when they run. I could cache the whole page dependent upon ... the maxID of the top record? The quantity of records returned when the page is specific to the user's imput?

    Now, is there any way to do this cache in 'reverse'? By which I mean, cache an entire page, but have one or two tiny pieces in the middle f it that say 'this part is not cached'? Or one would just have to use partial-template caching all 'around' those? Sorry for the simple questions, I'm new to this.

  2. PJ, your example of needing to cache most of a page exception a section or two is certainly possible. But it isn't full page caching because there are sections of the page that are not cached. So, to achieve your goal you would use partial page caching and cache only those page fragments necessary and leave the others alone.

  3. HP

    I know this is an old post, but there's a really good example of where this might be required. Many websites show different content to subscribers vs the general public in order to incentivise subscriptions, but that can be resource-intensive.

    So you could have:
    <cfif Session.IsSubscriber>
    ... [code to get all content]
    <cfelse>
    ... [code to get free content only]
    </cfif>
    #DisplayContent()#

    OR you could have:

    <cfcache action="cache" dependsOn="Session.IsSubscriber">
    ... [code to get allowed content]
    #DisplayContent()#
    </cfcache>