All four letters are usually exercised together by a single piece of real code, not tested one at a time. A funds transfer is the standard example because it needs every one of them:
public async Task TransferFundsAsync(string fromAccountId, string toAccountId, decimal amount)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted);
try
{
var from = await _dbContext.Accounts
.Where(a => a.Id == fromAccountId)
.FirstOrDefaultAsync()
?? throw new InvalidOperationException("Source account not found.");
if (from.Balance < amount)
throw new InvalidOperationException("Insufficient funds."); // Consistency: enforce a business rule
var to = await _dbContext.Accounts
.Where(a => a.Id == toAccountId)
.FirstOrDefaultAsync()
?? throw new InvalidOperationException("Destination account not found.");
from.Balance -= amount;
to.Balance += amount;
await _dbContext.SaveChangesAsync(); // Atomicity: both updates in one unit
await transaction.CommitAsync(); // Durability: guaranteed to survive from here on
// Isolation (Read Committed here) meant no other transaction saw the
// half-updated balances while this one was still in flight.
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
Notice that none of the four letters are things this code implements from scratch — they're guarantees the database engine provides, which the application code merely has to invoke correctly (wrap related writes in one transaction, choose an appropriate isolation level, check business rules before committing). Getting ACID "wrong" in practice almost always means forgetting to wrap something in a transaction at all, not misunderstanding what a transaction does once you're inside one.