Struct RepositoryEx

Source
pub struct RepositoryEx<T: AggregateRootEx> { /* private fields */ }
Expand description

Repository extension abstraction, for performing operations over aggregates that implement the AggregateRootEx trait.

§Examples

Building upon the Repository sample, this example shows how a repository object can be extended in order to support concepts from the AggregateRootEx trait.

use std::sync::Arc;

use ddd_rs::{
    application::{DomainEventHandler, ReadRepository, Repository, RepositoryEx},
    infrastructure::InMemoryRepository
};

// The aggregate below requires an action to be performed asynchronously, but doing so directly
// would require the aggregate root to:
//
// - Have a reference to one or many application services, thus breaching the isolation between
//   the Application and Domain layers;
// - Expect an async runtime, which is generally associated with I/O operations and
//   long-running tasks, to be available for the implementation of business rules.
//
// These can be seem as contrary to the modeling principles of DDD, since the domain model
// should be self-sufficient when enforcing its own business rules.
//
// Instead, the aggregate will register a domain event requesting the async action to be
// performed prior to being persisted to the repository.
#[derive(Clone, Debug, PartialEq)]
enum MyDomainEvent {
    AsyncActionRequested { action: String },
}

#[derive(ddd_rs::AggregateRoot, ddd_rs::Entity, Clone)]
struct MyEntity {
    #[entity(id)]
    id: u32,
    last_performed_action: Option<String>,
    #[aggregate_root(domain_events)]
    domain_events: Vec<MyDomainEvent>,
}

impl MyEntity {
    pub fn new(id: u32) -> Self {
        Self {
            id,
            last_performed_action: None,
            domain_events: Default::default(),
        }
    }

    pub fn request_async_action(&mut self, action: impl ToString) {
        let domain_event = MyDomainEvent::AsyncActionRequested { action: action.to_string() };

        self.register_domain_event(domain_event);
    }

    pub fn confirm_async_action_performed(&mut self, action: impl ToString) {
        self.last_performed_action.replace(action.to_string());
    }
}

// The domain event handler will usually be a context that holds references to all necessary
// services and providers to handle domain events.
struct MyDomainEventHandler {
    repository: Arc<dyn Repository<MyEntity>>,
}

impl MyDomainEventHandler {
    pub fn new(repository: Arc<dyn Repository<MyEntity>>) -> Self {
        Self { repository }
    }
}

#[async_trait::async_trait]
impl DomainEventHandler<MyEntity> for MyDomainEventHandler {
    async fn handle(&self, mut entity: MyEntity, event: MyDomainEvent) -> ddd_rs::Result<MyEntity> {
        let action = match event {
            MyDomainEvent::AsyncActionRequested { action, .. } => action,
        };

        // Perform the async action...

        entity.confirm_async_action_performed(action);

        self.repository.update(entity).await
    }
}


// Extend the basic repository to enable processing of domain events registered by the
// aggregate, upon persistence.
let repository = Arc::new(InMemoryRepository::new());
let domain_event_handler = Arc::new(MyDomainEventHandler::new(repository.clone()));

let repository_ex = RepositoryEx::new(domain_event_handler, repository);

// Create a new entity and request an async action.
let mut entity = MyEntity::new(42);

entity.request_async_action("foo");

// Assert that the action was not performed yet, but registered a domain event.
assert!(entity.last_performed_action.is_none());
assert_eq!(entity.domain_events.len(), 1);

// Persist the entity and assert that the action was performed as a result.
repository_ex.add(entity).await.unwrap();

let entity = repository_ex.get_by_id(42).await.unwrap().unwrap();

assert_eq!(entity.last_performed_action.unwrap(), "foo");
assert!(entity.domain_events.is_empty());

Implementations§

Source§

impl<T: AggregateRootEx> RepositoryEx<T>

Source

pub fn new( domain_event_handler: Arc<dyn DomainEventHandler<T>>, repository: Arc<dyn Repository<T>>, ) -> Self

Creates a new instance of the extended repository.

Trait Implementations§

Source§

impl<T: AggregateRootEx> ReadRepository<T> for RepositoryEx<T>

Source§

fn get_by_id<'life0, 'async_trait>( &'life0 self, id: <T as Entity>::Id, ) -> Pin<Box<dyn Future<Output = Result<Option<T>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Gets an entity with the given ID.
Source§

fn list<'life0, 'async_trait>( &'life0 self, skip: usize, take: usize, ) -> Pin<Box<dyn Future<Output = Result<Vec<T>>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Lists all entities within a given page.
Source§

fn count<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<usize>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Returns the total number of entities in the repository.
Source§

fn exists<'life0, 'async_trait>( &'life0 self, id: <T as Entity>::Id, ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Checks whether an entity with the given ID exists in the repository.
Source§

fn is_empty<'life0, 'async_trait>( &'life0 self, ) -> Pin<Box<dyn Future<Output = Result<bool>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Checks if the repository is empty.
Source§

impl<T: AggregateRootEx> Repository<T> for RepositoryEx<T>

Source§

fn add<'life0, 'async_trait>( &'life0 self, entity: T, ) -> Pin<Box<dyn Future<Output = Result<T>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Adds an entity to the repository.
Source§

fn update<'life0, 'async_trait>( &'life0 self, entity: T, ) -> Pin<Box<dyn Future<Output = Result<T>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Updates an entity on the repository.
Source§

fn delete<'life0, 'async_trait>( &'life0 self, entity: T, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait, 'life0: 'async_trait,

Deletes the entity from the repository.
Source§

fn add_range<'life0, 'async_trait>( &'life0 self, entities: Vec<T>, ) -> Pin<Box<dyn Future<Output = Result<Vec<T>>> + Send + 'async_trait>>
where Self: Sync + 'async_trait, 'life0: 'async_trait,

Adds the given entities to the repository.
Source§

fn update_range<'life0, 'async_trait>( &'life0 self, entities: Vec<T>, ) -> Pin<Box<dyn Future<Output = Result<Vec<T>>> + Send + 'async_trait>>
where Self: Sync + 'async_trait, 'life0: 'async_trait,

Updates the given entities on the repository.
Source§

fn delete_range<'life0, 'async_trait>( &'life0 self, entities: Vec<T>, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: Sync + 'async_trait, 'life0: 'async_trait,

Deletes the given entities from the repository.

Auto Trait Implementations§

§

impl<T> Freeze for RepositoryEx<T>

§

impl<T> !RefUnwindSafe for RepositoryEx<T>

§

impl<T> Send for RepositoryEx<T>

§

impl<T> Sync for RepositoryEx<T>

§

impl<T> Unpin for RepositoryEx<T>

§

impl<T> !UnwindSafe for RepositoryEx<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.