Skip to content

Repository & Unit of Work — Detailed#

Repository#

A type-specific, collection-like API for one aggregate:

interface OrderRepository {
  Optional<Order> findById(OrderId id);
  List<Order>     findByCustomer(CustomerId c);
  void            save(Order o);   // insert or update
  void            delete(OrderId id);
}
  • Returns domain entities, not rows or DTOs.
  • One repository per aggregate root. No repository for OrderLine.
  • Implementations live in adapter layer (SqlOrderRepo, InMemoryOrderRepo, MongoOrderRepo).

Unit of Work#

Scope of a logical transaction. Track all entities touched in a use case; commit / rollback atomically.

try (var uow = unitOfWorkFactory.start()) {
  var order = uow.orders().findById(id).orElseThrow();
  order.markPaid();
  uow.payments().save(payment);
  uow.commit();
} catch (Exception e) {
  // rollback is automatic on close-without-commit
}
  • Backed by a DB transaction (Postgres BEGIN/COMMIT, JPA EntityManager).
  • Often also flushes domain events at commit.

Why both#

  • Repository = collection-like, type-safe access.
  • Unit of Work = transactional boundary, single commit.
  • Without UoW, repositories independently commit and you lose atomicity.

Alternatives#

Repository + UoW Active Record DAO
Concept aggregate-level, app-owned tx one class = one table + persistence methods data-access object per table, no domain shape
Domain logic location domain entity the row class itself service layer
Test isolation swap repository for in-memory fake hard — bound to DB medium
Examples DDD code, .NET EF + UnitOfWork, SQLAlchemy session Rails ActiveRecord classic Java EE

Specification pattern (companion)#

When queries get complex, push them out of the repository:

interface Spec<T> { boolean isSatisfiedBy(T t); SqlFragment toSql(); }

class OverdueOrders implements Spec<Order> {
  public boolean isSatisfiedBy(Order o) { return o.dueDate.isBefore(now); }
}

repo.find(new OverdueOrders().and(new ByCustomer(c)));

The repository stays small; specifications become composable.

Anti-patterns#

  • God repository: findAll, findByX, findByY, findByXAndY... — refactor to specifications or smaller repos.
  • Repository leaks ORM types: returning Page<Entity> from Spring Data exposes paging behaviour to the domain; wrap or hide.
  • Mocking a real DB in unit tests: prefer an in-memory port fake; integration tests use real DB.
  • Bypassing the repository: use cases issuing raw SQL — the architectural rule is broken.

Where this shows up#

  • Every LLD problem with persistence — order, ticket, library member, room booking.
  • Twitter / Instagram / News feedTweetRepo, FeedRepo.
  • Payment gatewayPaymentIntentRepo, ChargeRepo.

Glossary & fundamentals#

Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.

Tag Concept What it is Page
HLD Event sourcing + CQRS commands -> events; separate read model event-sourcing-cqrs
LLD Repository + Unit of Work aggregate repos, transactional boundary repository-pattern
LLD DDD tactical entity / value object / aggregate / event ddd-tactical
LLD Structural patterns Adapter, Decorator, Facade, Proxy, Composite structural-patterns
LLD Concurrency primitives mutex, semaphore, RW lock, atomic, CAS concurrency-primitives
LLD Error handling exceptions vs Result, error boundaries error-handling