The repository pattern: hiding storage behind a domain interface

What a repository is, how it keeps business logic independent of the database, how to write one well, and the traps: leaky abstractions and generic repositories.

On the roadmap:Software Design · step 2: Design patterns

Lesson 5 of 6 · 9 min read · quiz · exercise

The problem

Business logic such as "an order can only be cancelled before shipping" should not care whether orders live in PostgreSQL, DynamoDB or a test double. If the rule code is full of SQL, ORM calls and table names, it is hard to read, hard to test and hard to change.

A repository is an object that looks like an in-memory collection of domain objects, and hides how they are loaded and saved.

Figure SDA-SQR-05-1 · Layers around a repository · © forcsguy.com

A small example

interface OrderRepository {
  nextId(): string;
  findById(id: string): Promise<Order | null>;
  findOpenForCustomer(customerId: string): Promise<Order[]>;
  save(order: Order): Promise<void>;
}

class InMemoryOrderRepository implements OrderRepository { /* a Map, for fast tests */ }
class PgOrderRepository implements OrderRepository { /* SQL here, and only here */ }

The application service receives an OrderRepository, so tests can pass the in-memory version and production passes the PostgreSQL one.

Guidelines

  • One repository per aggregate root. An order and its line items are saved together; do not give every table its own repository.
  • Name methods after business questions (findOpenForCustomer), not storage mechanics (selectWhere).
  • Return domain objects, not ORM entities or rows. Keep mapping inside the implementation.
  • Keep transactions in the application layer (or a unit of work), so one use case can change several aggregates atomically.
  • Mind the queries. Load what the use case needs in one go to avoid the N+1 problem, and give read-heavy screens a separate read model instead of bending the repository.

Figure SDA-SQR-05-2 · Writes through repositories, reads through a read model · © forcsguy.com

When it is not worth it

  • Tiny CRUD apps where the domain is just the table: the ORM already is the abstraction, and a repository adds ceremony.
  • Generic repositories (Repository<T> with find(filter)) just re-export the database through another name and defeat the purpose.
  • Pretending storage is interchangeable. Switching from SQL to a document store changes consistency and query models, not just the class behind the interface. Design the interface for what your domain needs and accept that some choices are real commitments.

Real-world reading

Check your understanding

Question 1 of 3

0 correct so far

What does a repository give the domain code?

Go deeper

  • Repository by Martin Fowler: the original catalogue entry
  • Domain-Driven Design by Eric Evans: repositories in the context of aggregates
  • Patterns of Enterprise Application Architecture by Martin Fowler: unit of work and data mapper

© 2026 forcsguy.com. Original lesson text and diagrams. Books and sites linked above belong to their authors.