Friday 10 January 2014

Random thought: decoupling transaction from tag-like syntax

G'day:
This one keeps popping in and out of my mind, and I've forgotten to ask about it for about a year now. It's about how the CFScript transaction construct mimics the tagginess of <cftransaction>. And whether it needs to / should do...?



To contextualise things, here's a bog-standard <cftransation> block:
<cftransaction action="begin">
    <cftry>
        <cfquery>
            <!--- first query that must be transactionalised with the subsequent one --->
        </cfquery>

        <!--- some code --->

        <cfquery>
            <!--- this query needs to be in the same transaction as the previous one --->
        </cfquery>

        <cftransaction action="commit" />

        <cfcatch>
            <cftransaction action="rollback" />
        </cfcatch>
    </cftry>
</cftransaction>

So we've got a coupla queries which interact with the DB in such a way that they need to be in a transaction. If anything happens after the first query but before the second query completes (app code erroring, the second query erroring), the whole operation is rolled back.

Analogous code in CFScript would be:

transaction action="begin" {
    try {
        new Query(/* etc */);

        // some code

        new Query(/* etc */);

        transaction action="commit";
    }
    catch (any e){
        transaction action="rollback";
    }
}

Straight forward, and completely analogous.

However just because tag-style code lends itself to surrounding functionality with matched tags (eg in this case, <cftransaction> and </cftransaction>), are we perhaps being too literal in the implementation of transaction by following the same approach?

Couldn't we have this instead?

myTransaction = new Transaction();

try {
    new Query(/* etc */);

    // some code

    new Query(/* etc */);

    myTransaction.commit();
}
catch (any e){
    myTransaction.rollback();
}


Why does the entire transaction need to be in a single code block? Generally it's fine, the block is small-ish (and I guess it's desirable to have transactions as compact as possible), and code can be refactored into methods and includes or whatever to make the block of a manageable size.

But I have had occasions in which the design of the code is such I'd like to open a transaction in one file, and close it in another. And I've seen other people in similar situations too.

One valid example (and thanks to Zac Spitzer for prompting my recollection here) is that when writing unit tests that must hit a DB (for whatever reason), starting a transaction in beforeTests() and rolling it back in afterTests() would be jolly handy!

My issue is that once I encounter a situation like this that I then need to work around to make the CFML work, I start to wonder if the CFML has to work that way. And in this case I wonder if we're doing this "block encapsulation" thing out of habit other than necessity?

Thoughts? Am I missing something? It's entirely likely that I am... I would never profess to be an expert when it comes to CFML's interaction with DBs, especially and this infrastructure sort of level.

Anyway, I just thought I'd pose the question. Posed.

--
Adam