Placing an order touches inventory, payment and the order record, each its own service with its own database. A teammate suggests wrapping the three calls in a distributed transaction with XA. What actually happens if you do that, and why is it avoided in a service architecture?
Two-phase commit can genuinely keep the all-or-nothing guarantee across databases: every participant first prepares — does the work and holds its locks without committing — and only commits once every participant has confirmed it can. The cost is in the word "hold": between prepare and commit, every participant keeps its locks, and if the coordinator crashes in that window, those locks are held until it comes back, not released on any timeout, because releasing them would break the promise the prepare made. Across three separate services, that means the whole system's availability becomes the coordinator's availability, and the slowest participant sets everyone's latency. Worse, a payment provider's HTTP API can't "prepare" in the XA sense at all — it isn't built to hold a reversible lock and wait for a second signal — so most of what you'd actually want inside the transaction can't join one. A saga, with local commits plus compensating actions, gives up atomicity on purpose in exchange for something that doesn't require every participant to be a database.