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.
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
Related Terms
Dependency Injection
A design pattern where objects receive their dependencies from an external source rather than creating them internally, enabling loose coupling and testability.
Service Layer Pattern
An architectural layer that defines the application's boundary and coordinates domain logic, transaction management, and cross-cutting concerns through service classes.
Factory Pattern
A creational design pattern that encapsulates object creation logic, allowing the client to request objects without knowing the concrete class or complex construction details.
CQRS (Command Query Responsibility Segregation)
An architectural pattern that uses separate models for reading data (queries) and writing data (commands), allowing each to be optimized independently.