1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
use std::collections::HashMap;
use crate::application::{ReadRepository, Repository};
use crate::domain::{AggregateRoot, Entity};
/// An in-memory implementation of [Repository], using a [HashMap].
///
/// # Examples
///
/// ```
/// use ddd_rs::application::{ReadRepository, Repository};
/// use ddd_rs::infrastructure::InMemoryRepository;
///
/// // By definition, only `AggregateRoot`s have repositories.
/// //
/// // Common entities must be retrieved and persisted through their associated aggregate roots.
/// #[derive(ddd_rs::AggregateRoot, ddd_rs::Entity, Clone)]
/// struct MyEntity {
/// #[entity(id)]
/// id: u32,
/// my_field: String,
/// }
///
/// impl MyEntity {
/// pub fn new(id: u32, my_field: impl ToString) -> Self {
/// Self {
/// id,
/// my_field: my_field.to_string(),
/// }
/// }
/// }
///
/// # tokio_test::block_on(async {
/// let repository: InMemoryRepository<MyEntity> = InMemoryRepository::new();
///
/// // Add some entities to the repository.
/// repository.add(MyEntity::new(1, "foo")).await.unwrap();
/// repository.add(MyEntity::new(2, "bar")).await.unwrap();
/// repository.add(MyEntity::new(3, "baz")).await.unwrap();
///
/// // Attempt to retrieve an entity by its ID.
/// let my_entity_2 = repository.get_by_id(2).await.unwrap();
///
/// assert!(my_entity_2.is_some());
/// assert_eq!(my_entity_2.as_ref().map(|e| e.my_field.as_str()), Some("bar"));
///
/// let mut my_entity_2 = my_entity_2.unwrap();
///
/// // Update the entity, then persist its changes.
/// my_entity_2.my_field = "qux".to_string();
///
/// let my_entity_2 = repository.update(my_entity_2).await.unwrap();
///
/// assert_eq!(my_entity_2.my_field.as_str(), "qux");
///
/// // Delete the entity permanently.
/// repository.delete(my_entity_2).await.unwrap();
///
/// // Assert it no longer exists.
/// assert!(!repository.exists(2).await.unwrap());
/// # })
/// ```
pub struct InMemoryRepository<T: AggregateRoot> {
entities: std::sync::RwLock<HashMap<<T as Entity>::Id, T>>,
}
impl<T: AggregateRoot> InMemoryRepository<T> {
/// Creates a new [InMemoryRepository].
pub fn new() -> Self {
Self {
entities: std::sync::RwLock::new(HashMap::new()),
}
}
}
impl<T: AggregateRoot> Default for InMemoryRepository<T> {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl<T: AggregateRoot + Clone> ReadRepository<T> for InMemoryRepository<T>
where
<T as Entity>::Id: std::hash::Hash + Eq,
{
async fn get_by_id(&self, id: <T as Entity>::Id) -> crate::Result<Option<T>> {
let ro_entities = self.entities.read().unwrap();
let entity = ro_entities.get(&id).cloned();
Ok(entity)
}
async fn list(&self, skip: usize, take: usize) -> crate::Result<Vec<T>> {
let ro_entities = self.entities.read().unwrap();
let entities = ro_entities
.values()
.skip(skip)
.take(take)
.cloned()
.collect();
Ok(entities)
}
async fn count(&self) -> crate::Result<usize> {
let ro_entities = self.entities.read().unwrap();
Ok(ro_entities.len())
}
}
#[async_trait::async_trait]
impl<T: AggregateRoot + Clone> Repository<T> for InMemoryRepository<T>
where
<T as Entity>::Id: std::hash::Hash + Eq,
{
async fn add(&self, entity: T) -> crate::Result<T> {
let mut wo_entities = self.entities.write().unwrap();
wo_entities.insert(entity.id().clone(), entity.clone());
Ok(entity)
}
async fn update(&self, entity: T) -> crate::Result<T> {
self.add(entity).await
}
async fn delete(&self, entity: T) -> crate::Result<()> {
let mut wo_entities = self.entities.write().unwrap();
wo_entities.remove(entity.id());
Ok(())
}
}