Repository Pattern
A collection-like interface between the domain and the data store. Know where the interface lives, why that direction matters, and how it makes services testable without a database.
Overview
A Repository is an object that behaves like an in-memory collection of domain objects while actually talking to a database. The service code says accounts.get(id), accounts.add(account) or accounts.find_overdrawn(), and never writes SQL, never opens a connection, and never knows which database is behind the call.
The pattern is in Martin Fowler's *Patterns of Enterprise Application Architecture* (2003), where the Repository chapter was written by Edward Hieatt and Rob Mee. Their definition is short: a Repository mediates between the domain and the data mapping layers using a collection-like interface for accessing domain objects. Two words carry the meaning. 'Mediates' means it sits between two layers and each side sees only it. 'Collection-like' means the interface is add, remove, get and find, not insert, update, select and join.
Interviewers use the pattern to check three things: whether you put the interface in the domain layer and the implementation in the infrastructure layer (that is the Dependency Inversion Principle in action), whether you can test a service with an in-memory repository, and whether you know where the pattern stops (no business rules inside, no leaking of the ORM out).
Where it sits
- Domain — Account, TransferService, the AccountRepository interface — Nothing outside itself
- Infrastructure — PostgresAccountRepository — The domain (it imports Account) and the database driver
- Tests — InMemoryAccountRepository — The domain
The arrows point inward. The infrastructure imports the domain; the domain imports nothing. That is what lets the domain be compiled and tested alone.
The interface looks like a collection
The query methods carry domain meaning. find_overdrawn() says what the business wants; the SQL that finds balance < 0 is a detail hidden in one implementation. When a service needs a new kind of lookup, you add a named method rather than exposing a general query language.
A runnable example
The domain defines the interface as a Python Protocol, which is a structural type: any class with the right methods satisfies it, with no inheritance needed. The in-memory implementation is complete enough to run the service and its test.
The test above needed no database, no fixtures and no mocks library. A Postgres implementation would map rows to Account objects and back, and the service would not change.
Repository, DAO and ORM
Three names come up together and interviewers like them told apart.
- Level — Domain: a collection of aggregates — Table: one class per table, CRUD methods — Library: maps classes to tables
- Returns — Domain objects with behavior — Rows or data transfer objects — Mapped entity objects
- Vocabulary — find_overdrawn() — select_by_balance_lt(0) — Query API or generated SQL
- Who defines the interface — The domain layer — The data layer — The library
- Relationship — Often implemented on top of a DAO or an ORM — A building block — A tool a repository may use inside
A repository can use an ORM inside its implementation and still be a repository. The rule is that the ORM's session, entity classes and query objects do not appear in the interface.
Unit of Work: who commits
A repository adds and gets objects, but a business operation often touches several objects and must save all of them or none. Fowler's Unit of Work is the object that maintains a list of objects affected by a business transaction and coordinates writing out the changes. The repository records what changed; the unit of work decides when to flush it, in one transaction.
ORMs usually bundle this: SQLAlchemy's docs describe its Session as an implementation of the unit of work pattern, tracking changed objects and flushing them before a query or a commit.
Where the pattern goes wrong
- Business rules inside the repository — Two homes for rules; tests need the database to reach them — Rules live on the domain object or in a service; the repository only loads and saves
- The ORM entity or query object escapes the interface — Every caller is now tied to the ORM; a fake cannot be written — Return domain objects; keep query building inside
- One generic Repository[T] exposed to services — Services see save, delete and a raw query method; domain vocabulary is lost — A specific interface per aggregate, with named finders
- A repository per table — The domain becomes a mirror of the schema — One repository per aggregate root
- Loading a whole collection to filter in memory — Correct on a test dict, slow on a real table — Named finders that push the filter to the store
What you will be able to do
- Define the pattern with Fowler's words and explain the two that matter: mediates, collection-like.
- Draw the two layers and say why the interface belongs to the domain.
- Write a service test against an in-memory repository, and explain why that is not a mock.
- Tell a Repository from a DAO and from an ORM in one table.
- Say what a Unit of Work adds and why a repository should not commit on every call.
Interview questions
- How does a Repository differ from a DAO?
- Which layer owns the repository interface, and why is that the Dependency Inversion Principle?
- When do you use a generic repository, and when a specific one?
- How would you test a service that depends on a repository?
- What is the Unit of Work pattern, and how does it relate to a repository?
Sign up free to read the full topic
More Low-Level Design topics
Start free on Cariara · Coding problems · System design studio