Skip to content

Latest commit

 

History

History
75 lines (53 loc) · 3.15 KB

File metadata and controls

75 lines (53 loc) · 3.15 KB

Domain-Driven Design (DDD) Tactical Patterns: Repositories

Definition

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.

Purpose and Importance

  • 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).

Repositories and Aggregates

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.

Repository Design Best Practices

  • Define a generic repository interface.
  • Implement the repository interface in the infrastructure layer.
  • Use the repository interface in the application layer.
  • Return Result types from repository methods to handle errors.
  • Use transactions to ensure data consistency.

Rust Example: OrderRepository

Let's define an OrderRepository trait and an in-memory implementation.

1. The Repository Trait

// 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>;
}

2. The In-Memory Implementation

// 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()
    }
}

Trade-offs of the Repository Pattern

  • 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.

Conclusion

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.