A while back, you might remember me freaking out a bit about handling database transactions in OOPhoto, my latest attempt at learning object oriented programming in ColdFusion. One of the things that is nice about procedural code is that you know where all the code is being called from (that page) and wrapping a whole bunch of code in CFTransaction tags is never a problem. When you are using object oriented programming, on the other hand, it's a totally different story; you never know where the objects are being called from. That's part of the beauty and the initial frustration of object oriented programming - the objects don't have and shouldn't have to know about anything else outside of themselves (well, almost nothing else).
Because of the environmental-agnostic properties of method execution in object oriented programming, what I ended up doing was creating two functions for methods that might require a database transaction. One uses transactions, the other one does not. Take, for example, the Save() method on my service objects; for saving a transient object (a non-cached object), I created two save methods:
I got the basis of this idea (using two methods) from a brief conversation that I had with Peter Bell a couple of weeks ago. At first, I thought I would end up having to duplicate the logic in both methods, which was disappointing to say the least. And, in fact, this is how I went about implementing the methods initially. But then, half way through my first SaveWithTransaction() method, it dawned on me - why not just create the CFTransaction tags and then turn around and call the existing Save() method. After all, the only added functionality I wanted in SaveWithTransaction() was the CFTransaction tag itself; every other piece of logic was already built into the existing Save() method.
And so it was that I created a SaveWithTransaction() method that looked like this:
<cffunction name="SaveWithTransaction" access="public" returntype="any" output="false" hint="I take a photo object and persist it (using a database transaction)."> <!--- Define arguments. ---> <cfargument name="Photo" type="any" required="true" hint="I am the photo object to be persisted." /> <!--- Wrap the whole interaction in a transaction. ---> <cftransaction action="begin"> <cftry> <!--- Because the functionality for saving already exists, let's just turn around and call our existing Save() method (that works in a non- transaction way). Return the object that is passed back from our Save() method. ---> <cfreturn THIS.Save( ARGUMENTS.Photo ) /> <!--- Catch any errors. ---> <cfcatch> <!--- Roll back transaction. ---> <cftransaction action="rollback" /> <!--- Rethrow error. ---> <cfrethrow /> </cfcatch> </cftry> </cftransaction> </cffunction>
As you can see, the SaveWithTransaction() method just adds the CFTransaction functionality including the transaction rollback if the save action errors out. Other than that, the method simply turns around and calls THIS.Save() which handles the object persistence implementation.
Because the method calls of an object are supposed to be environment-agnostic, I had to make it a rule that no "Save" method would turn around and invoke the transactional version of another object's save method. By enforcing this, I am ensuring that a transaction is only created at the primary object persistence level and not for any of the composed objects. What this ultimately means is that a transaction-involving method will only ever be invoked by the Controller, never by a member of the domain model itself.
I am very satisfied with this solution. It allows me to cleanly organize my transactions. But almost more importantly, it allows me to add parallel versions of a feature (ie. Save, Delete) without duplicating any of my logic. That just feels clean to me.
Want to use code from this post? Check out the license.