RTS questionnaire · Q32 · Repository & Unit of Work proof

Have you implemented design patterns such as Repository and Unit of Work?

A runnable review of both patterns as they are actually implemented: the repository contract that expresses intent, the transactional boundary per use case, how they compose with CQRS, specifications and decorators, and where the patterns should stop.

Status: reviewed Live repository & UoW workflow .NET · Java · Python Jump to live proof ↓

32. Repository and Unit of Work

Answer as submitted

“Yes — Repository and Unit of Work are patterns I have implemented across .NET, Java and Python codebases.”
Technical decision: expand. The submitted line claims the patterns and the three stacks without showing either. This page is the separate, detailed version: it shows the repository contract, the transactional boundary, how the two patterns compose with CQRS, the specification pattern and decorators, and the cases where adding a repository would make the code worse.

Expanded answer

Yes, in all three stacks, and the mechanics differ while the intent does not. Repository: the interface is declared by the domain and named for the question being asked (GetActiveByPlanAsync), never for the mechanism; complex filtering becomes a named specification instead of a leaking IQueryable; and the implementation is the only place that names EF Core, Spring Data JPA or SQLAlchemy. Unit of Work: one transactional boundary per use case — an explicit SaveChangesAsync in EF Core, the proxied @Transactional method and its persistence context in Spring, a session wrapped in a context manager in SQLAlchemy — with the outbox row written inside that same transaction so an event can never be published for a change that rolled back. The two compose with CQRS (write side through aggregates, read side through projections), with decorators for caching and logging so cross-cutting code never enters the repository, and with domain events raised inside the transaction but dispatched only after the commit. And they stop where they stop earning their place: no generic IRepository<T> CRUD over every entity, no repository wrapping a repository, no forcing a reporting screen through the domain model, and no abstraction over an ORM that is already an abstraction.

Aspect.NETJava / SpringPython
Repositorydomain interface + EF Core implementationport interface + Spring Data / JPA adapterProtocol + SQLAlchemy implementation
Unit of workDbContext change tracking + one SaveChangesproxied @Transactional method and persistence contextsession wrapped in a context manager
Query variationISpecification<T> to an expression treeSpring Data Specificationa select()-returning specification object
Cross-cuttingdecorator around the interface, registered in the composition rootAOP advice or an explicit decorator beandecorator implementing the same protocol
Eventsraised in the aggregate, published after SaveChanges@TransactionalEventListener(AFTER_COMMIT)staged on the UoW, published after commit()
Reporting readDapper projectionprojection query, readOnly = truecolumn projection, aggregate never materialised

Live repository & unit-of-work workflow proof

The running component below changes its pattern/result view through real event binding. Select an aspect to see the code shape, the reasoning behind the decision, and the evidence that it holds.

LIVE PREVIEW
RUNNING
REPOSITORY & UNIT OF WORKRUNNING

Choose an aspect of the two patterns

Select an aspect to inspect the code shape, the reasoning and the evidence.

$ ICustomerRepository → GetActiveByPlanAsync(plan, ct) · matching(ISpecification<Customer>)

The interface belongs to the domain and expresses intent, so the caller names the question it is asking instead of assembling a query object. The implementation lives in infrastructure, where EF Core, Dapper, Spring Data or SQLAlchemy can be swapped without the domain noticing.

query objects reaching the domain layer: 0
repository implementations per stack: 3 (EF Core, Spring Data JPA, SQLAlchemy)
public surface: intent-named methods + specifications, no IQueryable

What implementing the patterns buys

Rules I apply. Declare the repository where it is needed and name its methods for the question, not the query; put every varying predicate in a specification so the rule has one home; keep one commit per use case and stage events inside the transaction so a rollback cannot leave a published lie; make cross-cutting behaviour a decorator instead of an edit to the repository; send reporting through a projection rather than through aggregates; and refuse the generic CRUD base, because a repository that only forwards Add/Update/Delete is a layer that costs a layer and explains nothing.
Boundary for Q32: this page covers the Repository and Unit of Work patterns, their composition and their limits. Selecting between ORM and data-access frameworks (EF Core, Dapper, JPA, SQLAlchemy) is on the Q06 proof page, and SOLID and the wider catalogue of object-oriented patterns are Q33.

Source files

Select a file to inspect the proof with syntax highlighting and line numbers. The first file is the code running the component above; the others implement the two patterns in .NET, Java/Spring and Python, because the mechanics differ while the contract does not.

Acceptance criteria