⚛conceptIntermediateArchitecture
Repository Pattern
Definition
A design pattern that encapsulates data access logic, providing a collection-like interface for domain objects.
What is the Repository Pattern?
The Repository Pattern acts as an intermediary between the domain layer and data access layer. It provides a collection-like interface for accessing domain objects, hiding the complexities of database operations.
Core Principles
Separation of Concerns: Business logic doesn't know about SQL, ORMs, or database schemas.
Testability: Mock repositories for unit testing without touching real databases.
Flexibility: Swap database implementations (SQL → NoSQL) without changing business logic.
Structure
Controller → Service → Repository → Database
The Repository:
- Defines data operations (CRUD)
- Encapsulates query logic
- Returns domain objects, not raw database records
- Handles database-specific error translation
When to Use
Use Repository Pattern when:
- Application has complex data access logic
- Multiple data sources exist
- You need comprehensive unit testing
- Database implementation may change
Skip when:
- Application is simple CRUD
- Using an ORM that already provides abstraction
- Team lacks architectural discipline
Implementation Example
interface UserRepository {
findById(id: string): Promise<User>
save(user: User): Promise<void>
delete(id: string): Promise<void>
}
class PostgresUserRepository implements UserRepository {
async findById(id: string): Promise<User> {
// PostgreSQL-specific implementation
}
}
Business logic remains clean:
class UserService {
constructor(private repo: UserRepository) {}
async getUser(id: string): Promise<User> {
return this.repo.findById(id)
}
}
Common Pitfalls
- Over-abstraction: Don't create repositories for every table
- Leaky abstractions: Don't expose database-specific types
- Generic repositories: Avoid one-size-fits-all solutions
The Repository Pattern is about clarity, not ceremony.