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

ControllerServiceRepositoryDatabase

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.

Related Content