Aliaspooryorik
ColdFusion ORM Book

Controlling ColdBox event caching

I recently blogged about caching events and views in ColdBox 3. Luis was kind enough to comment that you can use the onRequestCapture interception point to manipulate the request collection that is used to control the event caching.

ColdBox has a feature called interceptors. These fire at particular points during the request lifecycle and can be used to handle things like Security. I'm quite new to ColdBox and haven't really looked at interceptors much other then the in-built Security interceptor so I thought it was about time I investigated further.

There are several pre-defined interception points, the one that Luis suggested to control caching is the onRequestCapture interception point which is new in ColdBox 3.

First off, we need to create a CFC which will be our interceptor. I'm going to call it cachingKey.cfc and put it in the interceptors directory in the root of your application. ColdBox uses a lot of conventions to define where certain CFCs should go, however the location of interceptors is not defined by convention so you can name and place your interceptor CFCs anywhere you like. Having said that, I would suggest that you stick with the common practice of putting them in an interceptors directory in the web root.


component name="cachingKey" extends="coldbox.system.interceptor"
{
/* INTERCEPTION POINTS
---------------------------------------------------- */


/**
* I am called at the onRequestCapture interception point
*/

void function onRequestCapture( required any event, required struct interceptData )
{
var rc = arguments.event.getCollection();
var prc = arguments.event.getCollection( private=true );
}
}

A couple of things to note, first cachingKey.cfc extends coldbox.system.interceptor. Secondly, there is a public method (if you don't define the access type in ColdFusion it's public by default), called onRequestCapture which accepts two arguments. It is important that we call the method onRequestCapture, so ColdBox knows when to fire this method.

To tell ColdBox about the cachingKey interceptor, you just define it in the Coldbox.cfc. In my example below, you'll see that I already have the autowire interceptor set up which ships with ColdBox 3.


//Register interceptors as an array, the order is important
interceptors = [
//Autowire
{
class="coldbox.system.interceptors.Autowire"
},
//cacheKey
{
class="interceptors.cachingKey",
properties={}
}
];

The class path needs to use dotted notation (as you normally would for class paths) and is from the web root, not the application root. If you want to be able to develop your application on your dev box, where it isn't running from the web root, but want it to work when you deploy it live, then I recommend setting up a per-application mapping. I use these all the time and think it's a really useful feature of ColdFusion 8+. You can set up per-application mappings in the Application.cfc like so:


this.mappings["/interceptors"] = COLDBOX_APP_ROOT_PATH & "interceptors/";

That's it, all the configuration is done. When you run your application (you'll may have to reload the config using index.cfm?fwreload=1), you see "interception [onRequestCapture]" in the ColdBox Debugging Information (if debugging is enabled!).

In my previous post I mention a scenario where I was building an e-commerce application which had a mini basket in the Layout.Main.cfm view which showed the number of items in the basket. As items were added to the basket I didn't want the event to cache as the number of items would be incorrect. As we are going to use the cachingKey interceptor to control caching of events, it makes sense to create a cached event and an "add to basket" event to simulate adding items to the basket so that we can see if it's working.


component name="General.cfc"
{
// Dependancy Injection
property name="sessionstorage" inject="coldbox:plugin:SessionStorage";

/**
* I add increment the basket items count
**/

void function addtobasket( required event )
{
var rc = arguments.event.getCollection();

sessionstorage.setvar( "basketitems", sessionstorage.getvar( "basketitems", 0 ) + 1 );

setNextEvent( "general" );
}


/**
* I am a cached event
* @cache true
* @cacheTimeout 10
* @cacheLastAccessTimeout 5
**/

void function index( required event )
{
var rc = arguments.event.getCollection();

arguments.event.setView( "General/index" );
}
}

Here's a simple layout


<!--- layouts/Layout.Main.cfm --->
<cfoutput>
<html>
<head>
<title>Coldbox caching example</title>
</head>
<body>
<p>Layout Rendered: #Now()#</p>
<p>Basket Items: #rc.basketitems#</p>
#renderView()#
</body>
</html>
</cfoutput>

And a simple view


<!--- views/General/index.cfm --->
<cfoutput>
<p>view rendered: #Now()#</p>
</cfoutput>

Now to update the interceptor so it actually does something!


component extends="coldbox.system.interceptor"
{
property name="sessionstorage" inject="coldbox:plugin:SessionStorage";

/* INTERCEPTION POINTS
---------------------------------------------------- */


/**
* I am called by the onRequestCapture interception
*/

void function onRequestCapture( required any event, required struct interceptData )
{
var rc = arguments.event.getCollection();

// simple way to control caching based on number of items in the basket
rc.basketitems = sessionstorage.getvar( "basketitems", 0 );
}

}

Now when we run the application, we get the following output:


Layout Rendered: {ts '2010-12-11 20:07:47'}
Basket Items: 0
view rendered: {ts '2010-12-11 20:07:47'}

Run the same event again and we get:


Layout Rendered: {ts '2010-12-11 20:07:47'}
Basket Items: 0
view rendered: {ts '2010-12-11 20:07:47'}

This is good news as it proves the event is being cached. So let's add something to our fake basket by calling index.cfm?event=general.addtobasket. The output is now:


Layout Rendered: {ts '2010-12-11 20:09:18'}
Basket Items: 1
view rendered: {ts '2010-12-11 20:09:18'}

Great, the interceptor altered the request collection on the following line:


rc.basketitems = sessionstorage.getvar( "basketitems", 0 );

ColdBox has noticed that the request collection has changed and did not get the event from the cache.

So, what happens if we remove an item from the the basket; how does the caching work? Well, there's one way to find out, let's add a new event to remove items.


/**
* I reduce the basket items count
**/

void function removefrombasket( required event )
{
var rc = arguments.event.getCollection();

sessionstorage.setvar( "basketitems", sessionstorage.getvar( "basketitems", 0 ) - 1 );

setNextEvent( "general" );
}

To make sure everything nothing is cached to start with, I'm going to restart ColdFusion. Yes, I know this is a bit extreme but I want to make sure that the session is empty as well as the cache. Now, when I run index.cfm the first time, after a short wait whilst the application starts, I get:


Layout Rendered: {ts '2010-12-11 20:27:06'}
Basket Items: 0
view rendered: {ts '2010-12-11 20:27:06'}

Run it again, and as we'd expect we get:


Layout Rendered: {ts '2010-12-11 20:27:06'}
Basket Items: 0
view rendered: {ts '2010-12-11 20:27:06'}

Now let's add an item to the basket with index.cfm?event.general.addtobasket


Layout Rendered: {ts '2010-12-11 20:28:38'}
Basket Items: 1
view rendered: {ts '2010-12-11 20:28:38'}

And once more for good luck!


Layout Rendered: {ts '2010-12-11 20:29:32'}
Basket Items: 2
view rendered: {ts '2010-12-11 20:29:32'}

OK, so let's now remove an item with index.cfm?event.general.addtobasket


Layout Rendered: {ts '2010-12-11 20:28:38'}
Basket Items: 1
view rendered: {ts '2010-12-11 20:28:38'}

Did you notice that the timestamp is from a previously cached event? Cool huh!

This is a very simple example of how you can control event caching by manipulating the request collection, but hopefully you can see how you could use it to cache events for a particular user or even browser. Hats off to Luis and the rest of ColdBox team for an awesome framework.


2 comments

  1. John,

    Great example. One thing to note is that one may need to add the session token or some way to make the event cached key unique to the rc in your interceptor. In your example, if two users that both have 1 item in their basket they would return the same cached view, which might not be the desired results.

    Curt

    Comment by Curt Gratz – December 13, 2010
  2. @Curt, thanks and a great point. I did think of that, but wanted to keep the example simple.

    When I build ColdBox apps with users, then I used to use the Main.onRequestStart method to get the user and store the object in the request collection, however that doesn't affect the caching, so I've started to do this in the interceptor instead.

    Comment by John Whish – December 14, 2010

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.

Please subscribe me to any further comments
 

Search

Wish List

Found something helpful & want to say ’thanks‘? Then visit my Amazon Wish List :)

Categories

Recent Posts