views:

374

answers:

7

I have to send an email, write to a file, and call a web service. To maintain consistency, all steps must happen. If any step throws an exception or errors out, all steps must be rolled back.

Before I go rolling my own object ACID engine, are there any commonly accepted patterns for implementing ACID semantics at the object level?

Better yet, are there any existing libraries I can use for the the .NET platform?

Edit: I know sending an email can't be undone, but failing to connect to the SMTP server is cause to kill the whole transaction. Also, I'd like this to be extensible for use with future actions.

+2  A: 

Windows Workflow Foundation has a concept of compensation (using Composite activity) when ACID semantics might not be appropriate..Off course, it has support for ACID transactions as well.

A good question is why bother with compensation? Isn’t one big ACID transaction with automatic rollback just as good? An ACID transaction is most appropriate when operations occur within the same database or within the same information system. It is also most appropriate when operations end quickly. When different companies and services are involved, defining the process in terms of the ACID semantics is often challenging. For it to be isolated and durable, you have to keep all resources of different companies locked for the duration of the task. This is frequently unreasonable, especially if the task is long. For it to be consistent and atomic, you need ad hoc compensation code.

Gulzar
+3  A: 

The last time I saw something like this was several years ago. The little bit that I remember about it is that it was using the command pattern and storing each command object in a queue. I think it was a LIFO stack.

So if the "transaction" failed, the engine would pop off a command object, undo the command, then destroy the command object. Repeat until the stack was empty. The stack got cleared if the "transaction" was successful.

Unfortunately, I don't remember more than that.

CSLA.NET implements a similar undo stack. That's the only example with code that I can think off the top of my head.

hectorsosajr
+1  A: 

Since you can't un-send an e-mail, and it's relatively inexpensive to write a file, I'd just do those things in the proper order:

  1. Try to write the file/write the file. If unssuccessful, stop, otherwise continue to:
  2. Call the web service. If unsuccessful, delete the file and stop, otherwise continue to:
  3. Send e-mail -- email is asynchronous anyhow, so you'd never really know if it was sent or not since most e-mail servers are set to retry for a couple of days if an error occurs and you never get back an acknowledgment that the e-mail went through even if it was successful.
Robert C. Barth
You may still be in trouble if the web service receives and processes the call but the response gets lost. I ran into this with eBay's AddItem call, which has the irreversible side effect of costing money.
Jeffrey Hantin
In the world of networking, never receiving the ACK is the same as a NACK, is it not? When this happens, often some manual intervention is necessary and unavoidable.
Robert C. Barth
+2  A: 

The simplest technique without relying heavily on an external library is prevalence. Periodically checkpoint by using serialization to take a snapshot of your state, then maintain a journal by serializing enough information on every side-effectful operation against your data to repeat it later. If something blows up, reload the most recent checkpoint, then re-apply all journal records written after that point.

For something more sophisticated, try software transactional memory. It may be somewhat clumsy to implement in current languages, but is quite powerful and may give you some additional concurrency techniques as well.

For irreversible operations like accessing a Web service or sending an email, you'll need to use compensating transactions: make another Web service call to cancel or update the results of the previous one, or perhaps send another email advising the recipient that things didn't work as intended.

Jeffrey Hantin
A: 

Two thoughts:

zweiterlinde
+1  A: 

One idea is to use JMS as the 'engine' and you can utilize JMS transactions (which can join existing transactions e.g. DB transaction). This starts to lead towards an async event-driven architecture which is probably a good thing unless we are talking simple apps - in which case you probably don't need to be asking the question.

An example of this would be simple account creation. For this you want to persist account information to the DB and also send the user an email for activation - but you want them in the same transaction for obvious reasons.

You should not put email sending code within the transaction because even though you may send the email - db transaction commit may fail for one reason or another. You also should not put email sending outside of the transaction (after commit) because email sending may fail leading to an orphan account.

So to use JMS in this scenario - put JMS sending code within the DB transaction and have it join that transaction. You are guaranteed message delivery. On the other end have something consuming the queue sending emails out. In case of email send failure, best option is to log/raise an alert - JMS will roll back and put message back into the queue for later consumption. i.e. to try and resend email once you have hopefully fixed whatever the issue is.

The critical thing is - DB record is consistent and email is eventually sent.

A: 

Also, A experimental project STM .NET was recently released. This projects adds transaction memory to C#. It actually modifies the CLR to support this.

Matthew Manela