Transaction Demarcation

Transactions are important, and I wanted to talk about them a little, particularly as they relate to ORM.  If you're on the cf-orm-dev mailing list you've undoubtedly seen this topic come up over and over again.  And probably started muting conversations when my name shows up.  But before digging into the details, we need to clear up a couple misconceptions, starting with no ORM context at all…

First, transactions are controlled in the business layer, even though they are typically implemented in the database.  What do I mean by this?  When you begin/commit/rollback a transaction, you're actually issuing commands to your database server.  It does the work for you.  But the transaction demarcation is controlled by the business logic of the application.  Only your business logic knows what series of SQL statements comprise a unit of work.  With client-server apps (including web apps) there is often a one-to-one correspondence between requests and transactions, but not always.

Consider a simple account signup form like we've all filled out a million times.  It looks like that over there to the right.  There is the core account information, and then there's the checkbox for subscribing to the mailing list.  So what is the unit(s) of work?  There are [at least] two: creating an account and signing an account up for updates.  This means that submitting this form should trigger a request with two transactions (one for each unit of work).

In plain English, when processing this form the account creation should be successful regardless of whether the subscription is successful.  Far more important to have the account set up and ready for use, since you can just reprompt the user about the subscription if the subscribe action fails.

If you were to implement this with simple SQL statments, your code might look something like this (I've omitted all architecture, validation and security – you shouldn't do that):

transaction {
  executeQuery('
    insert into account
      (name, username, password,
       email
      )
    values
      ($params.name, $params.username, $passhash,
       $params.email
      )
  ');
  accountId = executeQuery('
    <magic get-account-id query>
  ');
}
transaction {
  executeQuery('
    insert into subscription
      (accountId, type
      )
    values
      ($accountId, 'inside scoop'
      )
  ');
}

This is a (the?) fundamental concept of transactions.  Let me state that again: transaction demarcation is business logic.  It belongs in your business layer.  Not in your persistence layer and certainly not in your presentation layer.  Doing this demarcation can be a gnarly problem, but you're already doing the hard part: identifying the units of work.  If you're using an object backend then those backend business methods are very likely your units of work.  Now it's just a matter of applying transactionality.

The easiest way that I've found to address transaction demarcation is through Aspect Oriented Programming (AOP).  That's a big scary term that simply means "running code when stuff happens, without having to explicitly call it."  For example, I want to run transaction demarcation code whenever a business method is invoked.  Not that complicated  (As an aide, if you're using ColdSpring, my TransactionAdvice CFC will help you.)

AOP gives you transparent transactionality which matches your business rules (assuming you have business methods that correspond to units of work) which is a huge win.  It makes transactionality simply disappear.  You don't have to think about it, you don't have to worry about it's various problems, everything just works.  Using the signup example (with a bit of architecture, though still no validation or security), your UI controller might look like this:

acctId = request.beanFactory.getBean("accountService).createAccount(
  attributes.name,
  attributes.username,
  attributes.password,
  attributes.email
);
if (attributes.signUp) {
  request.beanFactory.getBean("subscriptionService").subscribe(
    acctId,
    'inside scoop'
  );
}

The implementation of 'createAccount' might look like this:

function createAccount(name, username, password, email) {
  executeQuery('
    insert into account
      (name, username, password,
       email
      )
    values
      ($params.name, $params.username, $passhash,
       $params.email
      )
  ');
  var accountId = executeQuery('
    <magic get-account-id query>
  ');
  return accountId;
}

Note that there is no transaction demarcation anywhere.  Instead the beanFactory object is going to use AOP to ensure that every method invoked on accountService or subscriptionService will execute in a transactional context.  Transparent transactionality is achieved.

But don't think that this is an inflexible solution.  AOP provides ways to exclude methods, so if you have some method that shouldn't be transactional you can just exclude it and then do manual demarcation.  So it's really the best of both worlds: totally transparent when you don't care, but with the ability to take back complete control when you do care.

The bottom line is that if you care about the correctness of your applications and the integrity of your clients' data, then transactions are required.  But that doesn't have to be a burden on you as a developer, since transaction demarcation can be made almost entirely transparent.  So there is really no excuse for avoiding comprehensive transaction demarcation in every app you build.  The correct mindset should be that a non-transactional write to your persistent store is a bug, no questions asked.

Now that that's out of the way, I'll dive into some of the ORM-specific problems transactions present (and solve!) in my next post.

One response to “Transaction Demarcation”

  1. marc esher

    Barney, if I had a , I'd be a billionaire (before I went to jail).

    Thanks for yesterday's TransactionAdvice.cfc post and today's follow-up. It's been an interesting discussion on the ORM list and it's good to see it distilled.