In Domain-Driven Design (DDD), a Repository is a tactical pattern that provides a mechanism for encapsulating the logic required to retrieve and persist Aggregates. It acts as a collection-like interface for Aggregates, abstracting away the complexities of data storage and retrieval from the Domain Model.
- Persistence Ignorance: Decouples the Domain Model from the underlying data storage technology.
- Encapsulate Data Access Logic: Keeps the Domain Model clean and focused on business logic.
- Manage Aggregate Lifecycle: Responsible for ensuring that Aggregates are loaded and saved correctly.
- Provide Collection-like Interface: Offers methods that resemble collection operations (e.g.,
add,get_by_id,remove).
A Repository should only work with Aggregate Roots. It should not be possible to retrieve an Entity or Value Object from the repository directly. This is because the Aggregate Root is responsible for maintaining the consistency of the entire Aggregate. If you could retrieve an Entity or Value Object directly, you could bypass the Aggregate Root and potentially violate the business invariants.
- Define a generic repository interface.
- Implement the repository interface in the infrastructure layer.
- Use the repository interface in the application layer.
- Return
Resulttypes from repository methods to handle errors. - Use transactions to ensure data consistency.
Let's define an OrderRepository trait and an in-memory implementation.
// src/domain/order_repository.rs
use crate::domain::Order;
pub trait OrderRepository {
fn save(&self, order: &Order);
fn find_by_id(&self, id: &str) -> Option<Order>;
}// src/infrastructure/in_memory_order_repository.rs
use crate::domain::{Order, OrderRepository};
use std::collections::HashMap;
use std::sync::Mutex;
#[derive(Default)]
pub struct InMemoryOrderRepository {
orders: Mutex<HashMap<String, Order>>,
}
impl OrderRepository for InMemoryOrderRepository {
fn save(&self, order: &Order) {
let mut orders = self.orders.lock().unwrap();
orders.insert(order.id.clone(), order.clone());
}
fn find_by_id(&self, id: &str) -> Option<Order> {
let orders = self.orders.lock().unwrap();
orders.get(id).cloned()
}
}- Increased Complexity: The Repository pattern can add complexity to the domain model.
- Performance Issues: The Repository pattern can sometimes lead to performance issues, especially when working with large Aggregates.
Repositories are a vital tactical pattern in DDD, providing a clean and robust way to manage the persistence of Aggregates. By abstracting data access concerns, they ensure that the Domain Model remains pure and focused on business logic, leading to highly maintainable, testable, and flexible applications.