2026-05-14
Repositories handle individual aggregate persistence, but real business operations touch multiple aggregates. Charge a customer, decrement inventory, write an audit log — three writes that must succeed or fail together. Sprinkle db.commit() across your service layer and you'll eventually ship a bug where the charge succeeded but inventory didn't.
The Unit of Work (UoW) pattern fixes this by tracking all changes made during a business operation and flushing them in a single transaction at the boundary. Your domain code calls uow.orders.add(order) and uow.inventory.decrement(sku, 1), but nothing hits the database until uow.commit() — and if anything throws, uow.rollback() undoes everything.
Concrete example. An e-commerce checkout:
OrderService opens a transaction, writes the order, calls InventoryService which opens its own transaction, then calls PaymentService. Three separate transactions. A network blip after step 2 leaves you with a paid order and stale inventory.Session, Entity Framework's DbContext, and Hibernate's Session are all UoW implementations — you've probably been using them without naming them.Typical shape:
with UnitOfWork() as uow:
order = uow.orders.get(order_id)
order.add_line(sku, qty)
uow.inventory.reserve(sku, qty)
uow.commit() # one transaction, all-or-nothing
The __exit__ rolls back on exception. Repositories share the same session/connection injected by the UoW, so they all participate in the same transaction.
Rule of thumb: one UoW per business operation, scoped to a single request or message handler. If a single operation needs N aggregate writes, you need exactly one commit at the end — not N. If you find yourself wanting nested transactions or commits inside a loop, you're either batching wrong or your aggregate boundaries are off.
Watch out for:
