Managing the Transfer Cache with Clones

In my recent adventures with Transfer, I ran into a problem where objects that weren't yet saved were showing up in my cached Transfer objects. Since I don't make mistakes (uh huh...) I immediately assumed this was a bug in Transfer, and went about documenting my problem on the Transfer group.

Well it turns out (surprise surprise) that this was by design, and so I had to make a few changes to my way of thinking in order to get my app to behave the way I expected it to. So here's a rundown of the "problem" and what was done to fix it (thanks to Matt Quackenbush for the fix).

How's the Cache Work?

Basically the Transfer cache keeps track of everything you do to an object, whether it's cached or not. This means that when I create a new object, it's immediately stored in the cache for each retrieval, and if I assign that object to a parent, it's stored with that parent in the cache, even before it gets saved.

My initial reaction to this was that it was bad behavior, but after ruminating over the possibilities, I changed my mind entirely. Being able to store complete transfer objects in cache by default would make it easy to do things like multi-page forms or shopping cart and order entry systems without needing to deal with the caching mechanism for it myself (such as putting an object in session).

The only issue is that if you do this, other parts of the application that can also see that object see non-saved data before you're ready for them to. That's alright if each user has their own unique "thing" that they're dealing with, but it's a big problem if people are sharing objects and data shouldn't be shared until it's validated and saved.

So What's my Problem

I have a Group object, which contains multiple Items. In my application, as part of the process of adding a new Item to a Group, I'm creating an empty Item object, and then assigning it to the right Group before I do anything else. The View then gets this object and the user can manipulate the empty and unsaved object. When the user clicks the Add button, the Item object is validated and then saved to the database if there are no errors. Here's the relevant bit of code that handles this:

")) />

When I go to my ItemService, grab an Item, and then set it's Parent Group using the setParentGroup method, Transfer updates the Group object in the cache with the new (non-saved) Item. If an error occurs later in the flow during my ItemService.Save method, then the item still stays in the cache, and I end up with an empty Item for every new Item that doesn't pass validation

Cloning to the Rescue

So to fix this problem we can use a clone of an object to do our manipulations before we save it into the database. According to the Transfer ORM documentation, the Clone() method on an object allows you to "... to make a deep clone of every generated TransferObject ...". This newly cloned object is "... outside of any caching that is currently in use within Transfer, which means that any changes that are done to this object, and are not saved, are only particular to the request that they are currently in." In addition to that, the really slick part is "...that Transfer.save() and Transfer.update() on a clone object will update the object currently in cache to the state of the saved object."

Exactly what I need... So with that information in hand, I changed just two lines of code, and the problem was fixed:

You should notice that I chained a Clone() method onto each of the getXXX methods in my services. This means that I get a deep copy of each to perform my operations. So when an error happens, it's happening to the objects in the current request, and not on the objects in the cache. As soon as I call the Service.Save method, validate the object, and then call he Transfer.Save method, all of the cache'd objects get magically updated for me so that they're available through the entire application.

And that's it... So the rule of thumb is, if you don't want your objects updated into the cache until they're saved, always use a Clone'd copy of them.

Posted by Daniel Short on Jul 21, 2008 at 12:00 AM | Categories: ColdFusion - Transfer -

2 Comments

Bob Silverberg

Bob Silverberg wrote on 07/21/08 2:27 PM

Great post, Dan. Based on the recent conversation on the Transfer google group, I was just thinking that we needed a blog post explaining about clone(). I imagine there are many Transfer users who are unaware of its importance. Just one thing I wanted to add; in your addItem function you're doing a great job of keeping Transfer decoupled from this object by using your ItemService. However, this new change now tightly couples this component with Transfer because you're calling clone() directly. I would consider looking at the getItem() method of your ItemService to see whether you can move the clone() into it. Oh, and what's up with the ASP blog software? Shame on you ;-)
Daniel Short

Daniel Short wrote on 07/21/08 2:27 PM

Thanks for the compliment. True that I've now required my Service to implement a Clone method for my controller to work... Just have to think this through and figure out if there would be times that I wouldn't want a clone back, and should that be an argument to my getXXX methods in my Service layer... Good catch. As for the ASP blog, the first post was made in July of 2002 (6 years ago!) and I've never had a reason to change it. It will eventually be converted to CF once I have by CCT chops honed :).