Problem
Domain and application code that talks to SQL, an ORM, or a document store directly becomes tied to storage details and hard to test. Query logic scatters across the codebase.
Also known as: repository, domain-repository
Provide a collection-like interface for retrieving and storing aggregates, hiding persistence details from the domain.
Domain and application code that talks to SQL, an ORM, or a document store directly becomes tied to storage details and hard to test. Query logic scatters across the codebase.
A repository mediates between the domain and data mapping layers. To the caller it behaves like a collection: ask for an order, change it, and put it back. The caller does not see SQL, connection handling, or ORM sessions.
The interface lives close to the domain and speaks in domain terms. The implementation sits in infrastructure.
class OrderRepository(Protocol):
def get(self, order_id: OrderId) -> Order: ...
def add(self, order: Order) -> None: ...
class SqlOrderRepository:
def get(self, order_id):
row = db.query("select * from orders where id = %s", order_id)
return to_domain(row)
UseCase -> OrderRepository (interface) <- SqlOrderRepository -> DB
<- InMemoryOrderRepository (tests)
Pair it with the unit of work to commit changes across several repositories atomically. For read-side screens, bypass the repository and use dedicated query services that return flat read models.
None — code abstraction
Low
Low to medium
Leaky abstraction exposing ORM query objects to callers
N+1 queries hidden behind a simple interface
Repository per entity breaking aggregate boundaries