Railo And AOP Transactions

If you're reading this, chances are good you're a software developer.  And that means you never touch the DB outside a transaction, right?  Of course, right.

Most of the time, transactions are a bit of a bear because their use is orthagonal to your business tasks.  To put that another way, business operations flow vertically from UI to backend to UI, and the various operations in your app typically flow mostly in separate parallel channels.  Transactions go across your application's structure, needing to be uniformly applied to all those operations, but being independent of all of them.  The "right" term for this is a "cross-cutting concern."

Fortunately, there are a pile of people in this world who are way smarter than I, and they've come up with Aspect Oriented Programming as a way to deal with these cross-cutting concerns.  The typical example shown is with logging, but transaction management is rather more useful, I think, isn't any more complicated, and is also more specific to AOP (whereas logging could be done with event-driven programming as well).  I'm not going to go into AOP, but if you're not using it, you should spend the time to learn a bit about it.  Really.

So the idea is to leverage AOP to automatically wrap your business methods with transactions.  In the CFML world, that means ColdSpring and its AOP support (which is modelled after Java's Spring framework).  Unfortunately, that's the easy part.  Since CFML servers abstract away DB connections (behind DSNs), it's hard to get ahold of the proper context to do the transaction demarcation correctly from your application code.  Somehow you have to know what the engine is doing behind the scenes so you can start/stop transactions at the right places.

When I wrote transactionadvice.cfc many years ago, I used a psuedo-ThreadLocal for tracking transactional method depth.  That way it can apply transactions at the top level, but skip them lower (nested) levels.  It works, but it doesn't handle multithreaded requests (i.e., using CFTHREAD) correctly, so it's far from ideal.  Using a real ThreadLocal would be better, but it requires Java or Groovy, and that's a dependency I don't want to require.  But for the majority of cases, it works fine.

Today, however, I discovered that Railo has this method:

getPageContext().getDataSourceManager().isAutoCommit()

It will tell you whether you're currently in autocommit (non-transaction) mode.  With this direct check of the transactional status, all the issues simply vanish, and the advice can be whittled down to this:

<cfcomponent extends="coldspring.aop.MethodInterceptor">

  <cffunction name="invokeMethod" access="public" output="false" returntype="any">
    <cfargument name="methodInvocation" type="coldspring.aop.MethodInvocation" required="true" />
    <cfif getPageContext().getDataSourceManager().isAutoCommit()>
      <cftransaction>
        <cfreturn methodInvocation.proceed() />
      </cftransaction>
    <cfelse>
      <cfreturn methodInvocation.proceed() />
    </cfif>
  </cffunction>

</cfcomponent>

Copiously excellent, if you ask me.  About the only thing that would make it better is if CFML grew a isAutoCommit() or isInTransaction() method so you don't have to dig into the underlying Java bits in an engine-specific way.

Comments are closed.