Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 47

    Unit of Work

    Unit of Work tracks changes to multiple aggregates during a business transaction and commits them atomically.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Unit of Work tracks changes to multiple aggregates during a business transaction and commits them atomically. One use case — transfer money across accounts — must write to 4 tables or none. Unit of Work defines the transaction boundary so partial commits don't corrupt state.

    The story

    A banking "transfer money" use case wrote to 4 tables. Half-commits during DB outages caused months of reconciliation. Explicit Unit of Work with transaction boundaries eliminated partial-write incidents.

    The business problem

    Multiple repository calls without atomic boundary cause partial writes:

    • Half-commits: debit succeeds, credit fails — money created/destroyed.
    • Scattered @Transactional: transaction boundaries unclear per use case.
    • Cross-aggregate inconsistency: invariants span multiple repos.
    • Reconciliation cost: manual fixes for partial failure states.

    The problem teams faced

    Unit of Work fits when:

    • Use case touches multiple aggregates/tables that must commit together.
    • Partial failure must roll back all changes atomically.
    • Repositories need shared persistence context (single DB session).
    • Transaction management shouldn't leak into every repository method.

    Understanding the topic

    Intent: Maintain list of affected objects and coordinate writing changes in one atomic operation.

    • UnitOfWork — registerNew/Dirty/Deleted; commit() flushes atomically.
    • Repositories — share same UoW/session — don't auto-commit individually.
    • Application service — opens UoW, orchestrates, commits or rolls back.
    • @Transactional — framework UoW (Spring) at use case boundary.

    Internal architecture

    Transfer with Unit of Work:

    ts
    class TransferMoneyUseCase {
    constructor(private uow: UnitOfWork) {}
    async execute(from: AccountId, to: AccountId, amount: Money) {
    await this.uow.begin()
    try {
    const a = await this.uow.accounts.findById(from)
    const b = await this.uow.accounts.findById(to)
    a.debit(amount); b.credit(amount)
    this.uow.registerDirty(a); this.uow.registerDirty(b)
    await this.uow.commit()
    } catch (e) {
    await this.uow.rollback()
    throw e
    }
    }
    }

    Visual explanation

    Three diagrams cover UoW boundary, commit/rollback, and vs repository alone:

    Unit of Work boundary
    Begin UoW
    Start transaction
    Repo A · Repo B
    Same session
    commit()
    All or nothing
    rollback()
    On any failure
    One business transaction — one commit point.
    Transfer without UoW
    Debit account
    Committed
    Network fail
    Before credit
    Money lost
    Partial state
    Reconciliation
    Months of pain
    Banking story — why atomic boundary matters.
    UoW vs per-repo commit
    Each repo commits?
    Danger
    Shared UoW
    Deferred flush
    Use case owns
    commit() once
    Spring @Transactional
    Framework UoW
    Repositories register changes; UoW commits once at end.

    Informative example

    Before / after — separate commits to Unit of Work:

    ts
    // ❌ Partial commit risk
    async function transfer(from, to, amount) {
    await accountRepo.debit(from, amount) // commits immediately
    await accountRepo.credit(to, amount) // fails — debit already committed
    }
    // ✅ Single transaction boundary
    async function transfer(from, to, amount) {
    return db.transaction(async (tx) => {
    await tx.debit(from, amount)
    await tx.credit(to, amount)
    }) // commit once or rollback all
    }

    Execution workflow

    1Unit of Work lifecycle
    1 / 4

    Begin

    Start transaction / UoW at use case entry.

    @Transactional or explicit begin.

    Real-world use

    Hibernate Session, Entity Framework DbContext, Spring @Transactional, SQL BEGIN/COMMIT blocks, TypeORM QueryRunner transactions.

    Production case study

    Banking transfer reconciliation elimination:

    • Symptom: partial writes during outages; months of reconciliation.
    • Decision: Unit of Work / explicit transaction per transfer use case.
    • Outcome: zero partial-write incidents post-migration.

    Trade-offs

    • Pro: atomic multi-aggregate commits; clear transaction boundary.
    • Con: long transactions if use case does slow I/O inside boundary.
    • Con: doesn't solve cross-service atomicity — need Saga.

    Decision framework

    • Use when use case writes multiple aggregates in one DB.
    • Use @Transactional at use case/service method boundary.
    • Avoid long-running work inside transaction — I/O outside, commit fast.
    • Cross-DB/service: Saga not UoW.

    Best practices

    • Transaction boundary = use case — not repository method.
    • Keep transactions short — no HTTP calls inside @Transactional.
    • Idempotent use cases when retry after ambiguous commit failure.

    Anti-patterns to avoid

    • @Transactional on every repository method — wrong granularity.
    • Nested transactions without understanding propagation — surprises.
    • UoW spanning HTTP request to external API — locks and timeouts.

    Common mistakes

    • Lazy loading outside transaction — LazyInitializationException class of bugs.
    • Read-only operations marked transactional unnecessarily — connection hold.

    Debugging tips

    • Log transaction begin/commit/rollback with use case name.
    • Trace which repo call triggered unexpected commit.

    Optimization strategies

    • Read-only @Transactional(readOnly=true) for query use cases.

    Common misconceptions

    • UoW ≠ Saga. UoW is single-DB atomicity. Saga is cross-service eventual consistency.
    • DbContext is UoW in Entity Framework terminology.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    3 questions
    1IntermediateQuestionUnit of Work vs Repository?+

    Answer

    Repository: persistence access per aggregate. UoW: coordinates multiple repo operations in one atomic commit.

    Follow-up

    Same DbContext?
    2AdvancedQuestionWhere put @Transactional?+

    Answer

    Use case / application service method — one business transaction. Not on every repository method.

    Follow-up

    Self-invocation trap?
    3AdvancedQuestionUoW across microservices?+

    Answer

    Doesn't work — no distributed ACID. Use Saga with compensations for cross-service.

    Follow-up

    2PC why avoided?

    Summary

    You can define transaction boundaries per use case and prevent partial writes. Tell the banking reconciliation story — if you can do that, you own Unit of Work.

    Ready to mark this lesson complete?Track your journey across the entire course.