By Peter Bell

isDirty - Efficient Caching

Often you want to cache information within your application to save the expense of a database read for that data for every page request. The question is, what happens when that information is edited and the cache is out of date?

There are many solutions to this, but one of the most elegant is the isDirty pattern. It relies on the fact that you are using the same application to edit the database as to cache the information which is often the case with a web application (you can do an extended version where the web app is notified by third parties if they modify the database, but that's a little more complex).

With an isDirty, if you don't have the information you need, you go and get it and then cache it - typically in session or application scope.

On the editing side, you add a call which sets a property somewhere to let you know that the information "is dirty" for a given object when you save any changes to that object. Then the next time you try to use the object, it'll see that it "is dirty" and will replace the cache with a query back to the database to get the updated information.

In this way you only have to requery the database when the data is changed.

The most difficult part of this pattern is keeping loose couping between your edit and your cache. One solution is to put the isDirty method within the factory. So, you call a factory to return your user information:

<cfscript>
// Get the users session ID from the session facade
Local.UserID = SessionService.getUserID();
// Get the populated user object from the factory
Local.ThisUser = MyFactory.CreateEntity("User","#Local.UserID#");
</cfscript>

The factory will then have an isDirty method that the update scripts can call and it will do something along with lines of:

<cfscript>
If (NOT IsDefined("VARIABLES.#Entity#.#Entity##ID#") OR (IsDefined("VARIABLES.isDirty.#Entity#.#Entity##ID#")))
// If we haven't cached the object or the object cache is dirty, create the object.
{ CREATE ENTITY CODE GOES HERE };
// Either way, return the object
RETURN THE OBJECT CODE GOES HERE;
</cfscript>

And the edit code would just have to know about the factory, so somewhere in User.save() or UserService.Save() you'd have:

<cfscript>
Myfactory.isDirty("User", "User#ID#");
</cfscript>

Comments
Peter,

I'm not sure I understand all the implications here; it looks like each user object is named according to the user id, and that the factory then looks for an object with this name? How is the object scoped, and who is responsible for checking the isDirty? It would have to be in a shared scope, wouldn't it, for the admin to flag it and the user to have access to it?

Thanks,
Edward
# Posted By Edward T | 6/26/06 1:28 PM
Ih this case, I'm using the factory to handle caching - which kind of makes sense.

The job of a factory is to create objects, so it is not unreasonable to allow the factory to also handle caching (this only really works for application scoped caching - session scoped caching would be done differently). I get myfactory to store the objects within its own variables scope so that they are safely hidden and the calling script doesn't care whether the object was cached or not (that's a performance consideration which is only of interest to the factory).

In terms of the isDirty, I'm suggesting giving the factory an IsDirty method. So, when you run UserService.Save() or User.Save() (depending on whether your save is via a service layer or an active record pattern), you set MyFactory.IsDirty(), passing the fact that it is a user that is dirty and passing in the ID of the user. Then the factory will save that into its private scoped VARIABLES.IsDirty.User, creating a User73 key which it sets to 1. Then the next time it tried to return user 73, if it is cached, it sees the record is dirty and instantiates the object from scratch which will in turn call the data to be pulled from the database by the appropriate DAO.

As for scoping, it may be naive, but it seems OK to me to just have a single factory in APPLICATION scope that you depend on. There is a real story in terms of reuse for all objects to avoid using the application scope as a blackboard as it creates an unnecessary dependency, but the solution works fine for my needs as I only have a single generator so I don't need to reuse at an object level, so a few conventions to simplify my applications are fine.

As such, it'd be APPLICATION.MyFactory you'd call for both the create and isdirty methods.

Make any more sense?!

Best Wishes,
Peter
# Posted By Peter Bell | 6/26/06 3:50 PM
Thanks, Peter - a LOT more sense! Sounds like a much more sophisticated approach. I've liked the posts you've put up in the past few days, and I've gotten quite a few good ideas from them. Keep up the good work!
# Posted By Edward T | 6/26/06 4:24 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.