Skip to content
Mogens Heller Grabe edited this page Nov 7, 2013 · 14 revisions

Distributed transactions?

Out of the box, Rebus will NOT use DTC. This is a deliberate design choice, because my opinion is that data consistency and resiliency against failures are are much better handled by being conscious about how those things work and what the consequences are.

Another thing is that distributed transactions are usually quite expensive, performance-wise, because of the communication overhead involved. Also, since DTC (as everyone else) is subject to the CAP theorem, it will have to give up availability (the 'A' in CAP) when a network partition occurs.

So, how does it work then?

To put it shortly: By committing transactions in the right order.

Scenarios

I am not in a transaction - I want to send a message

Nothing to worry about here - just bus.Send the message, and all is good.

I am not in a transaction - I want to either publish a message or send several messages

Since publishing a message with Rebus consists of sending the message directly to possibly many subscribers, these two operations are equivalent (unless you're using the RabbitMQ transport in its multicast incarnation - then a bus.Publish translates into one single publish in RabbitMQ).

So, if it's important to you that either ALL or NONE of the recipients/subscribers get the message, you must perform the operation in a transaction. You can do that by letting Rebus enlist in an ordinary ambient transaction like so:

using(var tx = new TransactionScope())
{
    // perform several calls to the queueing system in here
    bus.Send(something);
    bus.Send(somethingElse);
    bus.Publish(whatever);

    tx.Complete();
}

This will work, because Rebus' transports are nice enough to enlist a lightweight queue transaction in the ambient transaction, thus allowing all queue interactions to be performed withing that transaction, and thus committed/rolled back atomically.

I am in a Rebus message handler

In this special case, Rebus will create a queue transaction which will surround the entire processing of the message. This means that Rebus will

  • receive the incoming message
  • send/publish all outgoing messages

as a part of the same queue transaction.

This also means that if your work fails, the queue transaction is rolled back, and nothing is lost.

THE ONLY THING TO LOOK OUT FOR: is if your work SUCCEEDS, but committing the queue transaction fails, then the incoming message will be received again. But this time, you will already have performed your work - but none of your outgoing messages will have been sent.

In this case - and you may already have figured this out - you must keep adhering to the rules of how to implement idempotency, which state that you must perform the same externally visible actions, even when your internal state reveals that you have already processed the incoming message.

I want to use DTC!

If you want to use DTC, you'll have to do some work yourself. At the moment, no transport implementation exists for Rebus that can enlist in a distributed transaction.

It shouldn't be that hard to create one though, e.g. by grabbing the existing MsmqMessageQueue, copy the source code to a new class, e.g. DtcMsmqMessageQueue, and then use MSMQ in a way that enlists the queue transaction in a way that allows for DTC escalation.

Clone this wiki locally