Back to all terms
S1S2S3
State & Archintermediate

Repository Pattern

An abstraction layer that mediates between the domain/business logic and the data persistence layer, providing a collection-like interface for accessing domain objects.

Also known as: Data Repository, Repository Abstraction, Data Access Layer

Description

The Repository pattern provides an abstraction over data storage that presents a collection-like interface to the domain layer. Instead of domain logic directly executing SQL queries or calling ORM methods, it interacts with a repository that exposes methods like findById, findByFilter, save, and delete. The repository encapsulates all data access logic—query construction, caching, pagination, and data mapping—behind a clean interface. This separation allows the domain logic to remain storage-agnostic and makes it possible to swap data sources (PostgreSQL to MongoDB, API to local cache) without modifying business logic.

In practice, repositories are typically defined as interfaces (or abstract classes) in the domain layer and implemented in the infrastructure layer. A UserRepository interface might define findByEmail(email: string): Promise<User | null> and save(user: User): Promise<void>, with a PostgresUserRepository implementation that uses Prisma or Drizzle ORM under the hood. This inversion of dependency follows the Dependency Inversion Principle: high-level domain modules depend on abstractions, not on low-level database modules. The pattern is central to Clean Architecture, Hexagonal Architecture, and Domain-Driven Design.

Repositories provide several practical benefits: testability (inject a mock or in-memory repository during unit tests), consistency (all queries for a given entity type go through a single, auditable path), and encapsulation (complex query logic with joins, eager loading, and caching is hidden behind a simple interface). The anti-pattern to avoid is the "generic repository" that simply wraps every ORM method—this adds indirection without abstraction. Good repositories expose domain-meaningful methods (findActiveSubscriptionsByPlan) rather than generic query builders.

Prompt Snippet

Define repository interfaces in the domain layer using TypeScript abstract classes (e.g., abstract class OrderRepository with methods findById, findByCustomer, save, delete) and implement them in the infrastructure layer using Drizzle ORM. Register implementations via the dependency injection container (tsyringe) so that domain services receive the repository interface, not the concrete class. Write unit tests for domain services using in-memory repository implementations backed by a Map<string, Entity>, and integration tests using the real Drizzle implementation against a PostgreSQL testcontainer. Ensure repositories handle optimistic concurrency via a version column checked in UPDATE ... WHERE version = $currentVersion.

Tags

design-patternsdata-accessclean-architecturedddtestability