Agile Tortoise
Greg Pierce’s blog
« Perfunctory “I’m still alive” post LOL License »
Servoy: Transactions are awesome! I hate transactions!
It's a fact that transactions are important to data integrity in a relational database application. It's also a fact that I find them burdensome and not particularly friendly to integrate in my development process. Why? Well, in a development system, you really don't care about data integrity. It's not real data. You also want as little crud in your code as possible that obscure failure points -- like try/catch/finally blocks. And try/catch/finally blocks are typically how you go about managing transactions.
So, what to do? Well, I've decided to punt on transactions as much as possible. For the most part, transactions are necessary on more complex operations that alter data in a number of different tables. In my Servoy applications, I always write processing like this in a global method -- typically that takes records or id's in the parameters, and does all the heavy lifting. Then I write very simple methods on forms that attach to the buttons, throw up a confirmation dialog (maybe) and forward to the global.
In development, I do not incorporate transactions into these global methods. I call them directly, I test them, I look for failure points, without the lack of clarity caused by the try blocks and transactions. Then, in production, on my button-click methods (or where ever I'm calling from), instead of calling the global method directly, I call it via this wrapper method:
-
// globals.db_transactionWrapper method
-
var methodName = arguments[0];
-
var result;
-
// start the transaction
-
if( !databaseManager.hasTransaction() )
-
databaseManager.startTransaction()
-
// call the method with remaining arguments (up to 5 args supported, though you could add more cases
-
try {
-
switch(arguments.length)
-
{
-
case 1 : result = globals[methodName](); break;
-
case 2 : result = globals[methodName](arguments[1]); break;
-
case 3 : result = globals[methodName](arguments[1], arguments[2]); break;
-
case 4 : result = globals[methodName](arguments[1], arguments[2], arguments[3]); break;
-
case 5 : result = globals[methodName](arguments[1], arguments[2], arguments[3], arguments[4]); break;
-
case 6 : result = globals[methodName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); break;
-
default : throw 'ArgumentError';
-
}
-
}
-
catch(e)
-
{
-
// rollback if a failure occured
-
databaseManager.rollbackTransaction()
-
// re-throw the exception so you can handle it in your solution's on_error method.
-
throw(e);
-
}
-
-
// everything's good -- commit the transaction and return the result
-
databaseManager.commitTransaction();
-
return result;
Usage for this method looks like:
-
globals.db_transactionWrapper( [globalMethodName], [argumentsForGlobalMethod], etc.);
The wrapper method takes care of the transaction handling, and forwards the call on to the appropriate global method to do the work, returning the same result.
This has a number of advantages. Of course, as mentioned, it relieves you of managing transactions in all of your methods. It also gives you the flexibility to call the methods without transactions if desired -- or to nest the calls to multiple methods in a single transaction when appropriate.
April 10th, 2008 at 3:00 pm
Very Nice!. Also shows a good use of how to call methods dynamically, which some developers may not know.
April 11th, 2008 at 12:25 am
That’s a cool way of getting it done. And no, I didn’t know how to call a method dynamically. Thanks for sharing that Greg.