Using a Transfer decorator to log all database changes

This is a quick and dirty blog entry, just wanted to hop on and blog about a method Im using to log database changes or additions done via Transfer.

All of my Transfer objects have decorators that were auto-generated by the very slick Illudium PU36 Code Generator, which basically adds a simple validate method. Normally a transfer decorator will extend transfer.com.TransferDecorator. Well, I have changed all of my decorators to extend a custom decoratorUtility, so that I can create shared functions across all transfer objects. Here is what my utility looks like:


<cffunction name=<span class='cc_value'>"save"</span> access=<span class='cc_value'>"public"</span> returntype=<span class='cc_value'>"void"</span> output=<span class='cc_value'>"false"</span> hint=<span class='cc_value'>""</span>>
   <cfargument name=<span class='cc_value'>"logData"</span> default=<span class='cc_value'>""</span> required=<span class='cc_value'>"false"</span> hint=<span class='cc_value'>"Accepts a string to be logged as the 'action' instead of the default"</span> />
   <cfset var className = '' />
   <cfset var pkName = '' />
   <cfset var pkType = '' />
   <cfset var pkValue = '' />
   <cfset var action = '' />
   <cfset var user = getAuthUser() />
   <cfset var logDate = now() />
   <cfset var log = '' />
   
   <!--<span class='cc_comment'>-// Gather some information about what we will be saving //---></span>   <cfset className = getClassName() />
   <cfset pkName = getTransfer().getTransferMetadata(className).getPrimaryKey().getName() />
   <cfset pkType = getTransfer().getTransferMetadata(className).getPrimaryKey().getType() />
   
   <!--<span class='cc_comment'>-// Determine if this record is a create or an update //---></span>
   <cfif getIsPersisted()>
      <cfset action = 'Updated' />
   <cfelse>
      <cfset action = 'Created' />
   </cfif>
   
   <!--<span class='cc_comment'>-// Invoke the underlying Transfer save function //---></span>   <cfset getTransfer().save(this) />
   
   <!--<span class='cc_comment'>-// Get the primary key value //---></span>   <cfset pkValue = this.getID() />
   
   <!--<span class='cc_comment'>-// Construct and save our transaction log record //---></span>   <cfset log = getTransfer().new('transactionLog') />
   <cfset log.setCompanyID(0) />
   <cfset log.setLogItem(className) />
   <cfset log.setlogID(pkValue) />
   <cfset log.setlogDate(logDate) />
   <cfset log.setlogData(arguments.logData) />
   <cfset log.setlogType(action) />
   <cfset log.setlogIP(cgi.REMOTE_ADDR) />
   
   <!--<span class='cc_comment'>-// If the user is logged in, add to the log //---></span>   <cfif isNumeric(user)>
      <cfset log.setlogUser(user) />
   </cfif>      
   <cfset getTransfer().save(log) />      
</cffunction>

This simply overrides the auto-generated save() function and will first get information about the transfer object to be saved, log it (using transfer still), then invokes the built-in save function. Obviously this has possibilities outside of logging, you could do security checks here, you could add utility / encryption functions here (think encrypting all data in your database, and then decrypting it after reading all in 1 spot)

Anyone else doing something like this?

Getting a web hosting deal is not a problem. The problem is to decide between startlogic or bluehost or the complex netfirms.

Digg StumbleUpon Facebook Technorati Fav newsvine reddit FARK Google Bookmarks
  1. Paul Marcotte

    #1 by Paul Marcotte - March 6, 2008 at 10:19 AM

    Nice Stuff! I use a BaseTransferDecorator with methods like populate(), validate() and a generic save(), but I'm not running logging of audit trails. This is a great example of moving logic into the business object. Thanks for posting it. :)
  2. James Allen

    #2 by James Allen - March 6, 2008 at 10:19 AM

    Very nice post. The power in Transfer decorators is crazy, especially when using something like Brian Koteks observer code. Great example of how to think when building your business objects. Paul, regarding your comment. What kind of things do you do in your base class? I.E Does your validate function work out what it needs to validate? I would be very interested in seeing that code.
  3. Brian

    #3 by Brian - April 11, 2008 at 10:17 PM

    Isn't this what Coldspring's AOP is for? Wrapping existing functionality with some pre/post/around calls? I see the advantage in doing it at the save() level for Transfer; I'm just wondering how this compares to using AOP?
  4. Justice

    #4 by Justice - April 15, 2008 at 9:45 AM

    the only thing I have done thus far with AOP is to setup some remote functions from my service beans (which is awesome by the way). Honestly, a lot of the AOP terminology threw me for a loop when I read it a while back, I think I will re-visit that doc and see if I may be better served by using something like that to log instead. Thanks for the comments all!

Comments are closed.