Repository
Repository presents a collection-like interface for accessing domain objects while hiding persistence details. It keeps domain logic clean and testable and centralizes query logic, at the risk of redundancy in simple CRUD apps.
Repository is a data-access pattern that mediates between the domain model and the data-mapping layer, presenting a collection-like interface for retrieving and storing domain objects. To client code, a repository looks like an in-memory collection of aggregates; behind that facade it translates calls into queries and persistence operations. This isolates domain logic from the details of how and where data is stored.
How It Works
A repository exposes intention-revealing methods such as findById, findByEmail, add, and remove, all expressed in domain terms and returning fully formed domain objects rather than rows or DTOs. Internally it uses a data mapper, an ORM, or raw queries to fulfill these requests. Often there is one repository per aggregate root, and it is the only sanctioned route to load and save that aggregate, which keeps persistence concerns out of the domain and enforces consistency boundaries.
Repositories frequently accept Specification objects to express complex selection criteria without leaking query syntax, and they cooperate with a Unit of Work to coordinate changes within a transaction.
When to Use It
Use Repository when you want to keep domain logic free of persistence concerns, when you need to swap or mock data sources for testing, or when query logic would otherwise be duplicated across the codebase. It is a cornerstone of domain-driven design and layered architectures, and it is valuable when the same aggregates are accessed from many places.
Trade-offs
A repository adds a layer of abstraction that can feel redundant for simple CRUD applications where an ORM already provides a usable interface; in those cases it risks becoming a thin, leaky pass-through. Designing a clean interface that does not expose query specifics is harder than it looks, and overly generic repositories often grow many ad hoc methods. Mapping rich queries through the abstraction can hurt performance if it prevents database-specific optimizations.
Related Patterns
Unit of Work tracks changes to objects loaded through repositories and commits them as one transaction. Data Mapper is the layer a repository typically delegates to for object-to-table translation. Specification lets callers express selection rules that the repository turns into queries. Repository contrasts with Active Record, which fuses persistence into the domain object itself.
Example
An OrderRepository offers findById, findPendingForCustomer, and save. Application services work only with Order aggregates and never write SQL. Switching the backing store, for instance migrating from MongoDB to PostgreSQL, changes only the repository implementation, leaving the domain and service layers untouched.