I’ve talked before about adopting a failure-oriented mindset. That means you should expect every component of your system or application to someday fail. In fact, they’ll usually fail at the worst possible times.
When a component does fail, whatever unit of work it’s processing at the time will most likely be lost. If that unit of work is backed up by a transactional database, well, you’re in luck. The database will do it’s Omega-13 bit on the transaction and it’ll be like nothing ever happened.
Of course, if you’ve got more than one database, then you either need two-phase commit or pixie dust. (OK, compensating transactions can help, too, but only if the thing that failed isn’t the thing that would do the compensating transaction.)
I don’t favor distributed transactions, for a lot of reasons. They’re not scalable, and I find that the risk of deadlock goes way up when you’ve got multiple systems accessing multiple databases. Yes, uniform lock ordering will prevent that, but I never want to trust my own application’s stability to good coding practices in other people’s apps.
Besides, enterprise integration through database access is just… icky.
Messaging is the way to go. Messaging offers superior scalability, better response time to users, and better resilience against partial system failures. It also provides enough spatial, temporal, and logical decoupling between systems that you can evolve the endpoints independently.
Udi Dahan has published an excellent article with several patterns for robust messaging. It’s worth reading, and studying. He addresses the real-world issues you’ll encounter when building messaging apps, such as giant messages clogging up your queue, or "poison" messages that sit in the front of the queue causing errors and subsequent rollbacks.