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.