ddd_rs/application/service.rs
1use crate::domain::AggregateRootEx;
2
3/// Trait for representing a **Request**.
4///
5/// Requests are usually [Commands](Command) or [Queries](Query), and their sole requirement is to
6/// have an associated `Response` type.
7pub trait Request: Send {
8 /// Request response type.
9 type Response: Send;
10}
11
12/// Trait for a [Request] that represents a **Command**.
13///
14/// > Change the state of a system but do not return a value.
15pub trait Command: Send {}
16
17impl<T: Command> Request for T {
18 type Response = ();
19}
20
21/// Alias for a [Request] that represents a **Query**.
22///
23/// > Return a result and do not change the observable state of the system (are free of side
24/// > effects).
25pub use Request as Query;
26
27/// Trait for representing a **Request Handler**.
28///
29/// See [Request] for more information about [Commands](Command) and [Queries](Query).
30///
31/// # Examples
32///
33/// ```
34/// use std::sync::Mutex;
35///
36/// use ddd_rs::application::{Command, CommandHandler, Query, QueryHandler};
37///
38/// // Error type for the Fibonacci service.
39/// //
40/// // It is required to implement the `std::error::Error` trait.
41///
42/// #[derive(Debug, PartialEq)]
43/// struct FibonacciError(&'static str);
44///
45/// impl std::fmt::Display for FibonacciError {
46/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47/// self.0.fmt(f)
48/// }
49/// }
50///
51/// impl std::error::Error for FibonacciError {}
52///
53/// // Fibonacci service.
54/// //
55/// // For demonstration purposes, it is implemented as a stateful service.
56///
57/// #[derive(Default)]
58/// struct FibonacciService {
59/// n: Mutex<u32>,
60/// }
61///
62/// impl FibonacciService {
63/// fn set(&self, n: u32) {
64/// let mut current = self.n.lock().unwrap();
65///
66/// *current = n;
67/// }
68///
69/// fn get_next(&self) -> u32 {
70/// let mut current = self.n.lock().unwrap();
71///
72/// let next = Self::fibonacci(*current);
73///
74/// *current = *current + 1;
75///
76/// next
77/// }
78///
79/// fn fibonacci(n: u32) -> u32 {
80/// match n {
81/// 0 => 0,
82/// 1 => 1,
83/// _ => Self::fibonacci(n - 1) + Self::fibonacci(n - 2),
84/// }
85/// }
86/// }
87///
88/// // Sets the current 0-based element of the Fibonacci sequence.
89/// //
90/// // `Command`s usually do not return a value, so their `Response` type is automatically `()`.
91///
92/// struct SetFibonacciCommand {
93/// n: u32,
94/// }
95///
96/// impl Command for SetFibonacciCommand {}
97///
98/// #[async_trait::async_trait]
99/// impl CommandHandler<SetFibonacciCommand> for FibonacciService {
100/// type Error = FibonacciError;
101///
102/// async fn handle(&self, command: SetFibonacciCommand) -> Result<(), Self::Error> {
103/// self.set(command.n);
104///
105/// Ok(())
106/// }
107/// }
108///
109/// // Gets the next element of the Fibonacci sequence.
110/// //
111/// // `Query`s are issued in order to retrieve a value, but without causing any side-effects to the
112/// // underlying state of the system.
113/// //
114/// // The more general `Request` trait can be used for actions that have side-effects but also
115/// // require a value to be returned as its result.
116///
117/// struct GetNextFibonacciQuery;
118///
119/// impl Query for GetNextFibonacciQuery {
120/// type Response = u32;
121/// }
122///
123/// #[async_trait::async_trait]
124/// impl QueryHandler<GetNextFibonacciQuery> for FibonacciService {
125/// type Error = FibonacciError;
126///
127/// async fn handle(&self, _query: GetNextFibonacciQuery) -> Result<u32, Self::Error> {
128/// Ok(self.get_next())
129/// }
130/// }
131///
132/// // Finally, instantiate and perform `Request`s to the `FibonacciService`.
133///
134/// # tokio_test::block_on(async {
135/// let fibonacci = FibonacciService::default();
136///
137/// assert_eq!(fibonacci.handle(SetFibonacciCommand { n: 10 }).await, Ok(()));
138/// assert_eq!(fibonacci.handle(GetNextFibonacciQuery).await, Ok(55));
139/// assert_eq!(fibonacci.handle(GetNextFibonacciQuery).await, Ok(89));
140/// assert_eq!(fibonacci.handle(GetNextFibonacciQuery).await, Ok(144));
141/// # })
142/// ```
143#[async_trait::async_trait]
144pub trait RequestHandler<T: Request>: Send + Sync {
145 /// Request handler error type.
146 type Error: std::error::Error;
147
148 /// Handles the incoming [Request], returning its [Response](Request::Response) as a [Result].
149 async fn handle(&self, request: T) -> Result<<T as Request>::Response, Self::Error>;
150}
151
152/// Alias for a [RequestHandler] specific to [Commands](Command).
153pub use RequestHandler as CommandHandler;
154
155/// Alias for a [RequestHandler] specific to [Queries](Query).
156pub use RequestHandler as QueryHandler;
157
158/// Trait for representing a **Domain Event Handler**.
159///
160/// See [AggregateRootEx] for more information about **Domain Events** and the example for
161/// [RepositoryEx](super::RepositoryEx) about this trait's usage.
162#[async_trait::async_trait]
163pub trait DomainEventHandler<T: AggregateRootEx>: Send + Sync {
164 /// Handles the incoming domain event, applying any necessary changes to the entity.
165 async fn handle(&self, entity: T, event: T::DomainEvent) -> crate::Result<T>;
166}