ProgramGuru

Unit of Work Pattern

Unit of Work: separate a useful responsibility

See the responsibility boundary

Business operationGroups related changes
Unit of WorkTracks or stages those changes
Supported transactionCommits under the store’s guarantees
ResultReports success or rollback/conflict
Business operation: Groups related changes → Unit of Work: Tracks or stages those changes → Supported transaction: Commits under the store’s guarantees → Result: Reports success or rollback/conflict

One operation changes a customer record and an order. Saving each change immediately can leave a partial result. The code needs a clear boundary for the changes that belong to one supported transaction.

This application-pattern lesson connects local organization with the system contracts discussed elsewhere in the course. The code is an original small local example, not a production persistence or web framework.

What you’ll learn

  • Explain the responsibility Unit of Work separates.
  • Follow its local example and identify the implementation boundary.
  • Compare related patterns and test the required guarantees.

How the responsibility is divided

Unit of Work tracks changes associated with a business operation and coordinates their persistence under a defined commit boundary. Implementations can track changed entities, stage explicit operations or integrate with an ORM’s transaction/session mechanism.

The example stages updates and produces a new in-memory Map at commit. It demonstrates a grouping boundary only. A database implementation must use its supported transaction rather than copying this as an atomic multi-service protocol.

Follow a small JavaScript example

Read the program and compare the expected output. Its state is local to one execution context; a real implementation needs the persistence, request and effect contracts described in the trade-offs.

JavaScript · local teaching example

let store = new Map([['customer', 'old'], ['order', 'draft']]);
const staged = new Map();
staged.set('customer', 'updated');
staged.set('order', 'submitted');
function commit() {
  const next = new Map(store);
  for (const [key, value] of staged) next.set(key, value);
  store = next;
}
console.log(store.get('order'));
commit();
console.log(store.get('order'));

Expected output

draft
submitted

Choose a boundary from the situation

Compare boundary choices

Related changes live in one transaction-capable database.
The operation also charges an external provider.

Compare the visible consequences. With JavaScript enabled, choose an option in each scenario to assemble your decision record.

This is an exploratory design exercise. It does not run infrastructure, measure capacity or replace the lesson’s quiz.

Choose the responsibility and examine what it does and does not promise.

All alternatives and consequences remain visible. Select one per situation to record your reasoning.

Choose it for a concrete need

Use it to make transaction ownership and grouped persistence explicit, especially when related repositories participate in the same supported store transaction.

Long-lived units can hold locks or stale tracked objects. Define commit errors, conflict retries and cleanup. A local Unit of Work cannot make unrelated databases, a payment API and a broker one atomic resource merely because the calls are grouped.

Compare the neighbouring responsibilities

Repository supplies domain-oriented access. Unit of Work coordinates persistence for an operation. Saga handles multi-owner local commits through a business recovery contract.

Continue with domain-oriented persistence access, recovery across separate owners, transaction isolation and invariants.

For primary further reading, use the Unit of Work catalog entry and compare the responsibilities with this course’s original example.

Check your pattern reasoning

What must justify a Unit of Work’s atomicity claim?
Choose one answer. You can try again.

Answer and explanation

The actual supported transaction and participant boundary.

Grouping code alone is not the commit mechanism.

Check your pattern reasoning

Why avoid an unnecessarily long-lived unit?
Choose one answer. You can try again.

Answer and explanation

It can retain stale state or occupy transaction resources.

Lifetime is an operating and consistency decision.

Keep the boundary and its guarantees explicit

Use Unit of Work when its responsibility matches the actual problem. Keep implementation lifetime, concurrency and error behaviour visible rather than treating an organizational label as proof.

Return to the design-pattern guide or apply this alongside transaction isolation and invariants.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Model–View–Controller Pattern →