Transacted APIs - A Migration Process

Let me start with a short story. I presented a while ago to some of my friends how cool System.Transactions and its TransactionScope are and how easy it is to use transactions with this object model. Not long after that, they got back too me very upset that "it doesn't actually work!?". In my team, we are testing this thing very thorough, so what could it be? I looked at their code and what they did was to simply wrap their existing calls inside a TransactionScope. This existing code was doing various things like updating a log file, sending an email, updating a file in a source control system and finally updating a database. They were unhappy because when an exception was thrown in the middle of these actions, the transaction didn't rollback the actions that were already completed.

Oh well, this might be sad because not all the APIs out there are transaction aware. And they are not going to change overnight to be transaction aware. In fact, it is wrong to change the existing APIs to be transaction aware if they were not in the first place. Here is why, let's say we have a function, called ClientRequest, which for each call does the following:
1. Update database DB_A
2. Update database DB_B
3. SomeAPIThatWritesToLog(timestamp); // writes an entry to a log with the timestamp when the call was made; the purpose of the entry is to log that the function was called and it doesn't need to say anything about the success of the call.

And now let's say that the function is called within a transaction (using an ambient transaction mechanism - see also https://blogs.msdn.com/florinlazar/archive/2005/04/19/409570.aspx). The developer of the function knows that the updates to the database will be enlisted in the transaction, and they will rollback if something goes wrong. He/She also knows that SomeAPIThatWritesToLog is not participating in the transaction, so the log will always be updated, independent of the outcome of the transaction.

Now, if the implementer of SomeAPIThatWritesToLog updates the function to be transaction aware, and participate in a transaction when one exists, then suddenly the ClientRequest will break its semantics. ClientRequest will not longer guarantee to have a per-call entry in the log with the timestamp; this is because when the transaction aborts, the SomeAPIThatWritesToLog will not do anything.

What is the solution in this case? One solution is to create a separate function, maybe called TransactedSomeAPIThatWritesToLog, which will be transaction aware and make sure you document that it is transaction aware.

Also a good practice for when you are in an environment that uses transactions is to make sure you separate that code that you want to execute independent of the existing ambient transaction. You do this either by suppressing the existing ambient transaction or by creating a new one (which method is the best depends on each case):
1. Update database DB_A
2. Update database DB_B
3. Suppress Ambient Tx
{
3.1. SomeAPIThatWritesToLog(timestamp);
}

Transactions become this way part of the interface contract. You can't break this contract without breaking existing apps.

Comments