The Unit of Work Pattern: Coordinate Changes as a Single Transaction

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:

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:

See it in action: Check out 21 Implement Unit of Work by TELCOMA Global to see this theory applied.
Key Takeaway: Wrap each business operation in a single Unit of Work so all changes commit together or not at all — never let "succeeded halfway" be a possible outcome.

All newsletters