In this lesson, you will learn how to evaluate the tension between immediate atomicity via distributed transactions and the high-availability demands of event-driven, eventually consistent systems.
The Conflict: ACID vs. CAP
When your C# background services span multiple microservices, you face a fundamental choice: do you lock resources across the network to ensure a "Distributed Transaction" (ACID), or do you accept "Eventual Consistency" via message queues?
Distributed transactions (such as those using two-phase commit protocols) enforce absolute integrity but suffer from high latency and the risk of blocking resources if a participant fails. In modern distributed systems, these are often considered anti-patterns because they couple the availability of your services to the slowest member of the transaction.
The Event-Driven Alternative
Instead of wrapping multiple service calls in a single transaction, we adopt an asynchronous model. We perform the local work, commit it to our primary database, and publish an event to a message broker.
Consider a simple order service:
public async Task ProcessOrder(Order order)
{
// 1. Transactional local commit
await _dbContext.Orders.AddAsync(order);
await _dbContext.SaveChangesAsync();
// 2. Publish event for downstream consumers
await _messageBus.Publish(new OrderPlacedEvent(order.Id));
}
In this model, the system is eventually consistent. The order is recorded immediately, but inventory or shipping services update their state milliseconds or seconds later. If the message bus is unavailable, your Background Service (using the Outbox Pattern) will retry until the message is delivered, ensuring integrity without locking the entire business process.
Trade-offs
- Distributed Transactions: Provide immediate visibility but create tight coupling and performance bottlenecks. They require synchronous availability of all participating nodes.
- Eventual Consistency: Embraces Background Service architecture. It increases system resilience and throughput but shifts the burden to the developer to handle duplicate messages (Idempotent Consumers) and potential temporary inconsistencies in the UI.
By moving away from distributed locks and toward message-based coordination, your background workers gain the autonomy needed to scale independently.
Key Takeaway: Replace rigid distributed transactions with asynchronous message-driven patterns to prioritize system availability, while using idempotency and the Outbox pattern to ensure transactional integrity.