architecturekit/decider.rs
1use std::{any, fmt, future::Future, pin::Pin};
2
3use crate::State;
4
5#[cfg(test)]
6mod tests;
7
8type Decision<S, D> = Result<Vec<<S as State>::Event>, D>;
9type Decide<C, S, D> =
10 dyn Fn(C, S) -> Pin<Box<dyn Future<Output = Decision<S, D>> + Send>> + Send + Sync;
11
12/// A decision on a command, given the state of its subject: the events to
13/// write, or an error if the command is refused.
14///
15/// Create one from a function that receives the command and the state, and
16/// returns the events in a future, so that it can load what else it needs to
17/// decide, such as the state of another subject:
18///
19/// ```
20/// # use architecturekit::{Event, Events, State};
21/// # use serde::{Deserialize, Serialize};
22/// # #[derive(Serialize, Deserialize, Event)]
23/// # #[event(type = "io.eventsourcingdb.library.book-borrowed")]
24/// # struct BookBorrowed { reader: String }
25/// # #[derive(Events)]
26/// # enum BookEvent { Borrowed(BookBorrowed) }
27/// # #[derive(Clone, Default)]
28/// # struct Book { is_borrowed: bool }
29/// # impl State for Book {
30/// # type Event = BookEvent;
31/// # fn evolve(self, event: &BookEvent) -> Self { Book { is_borrowed: true } }
32/// # }
33/// # #[derive(Clone)]
34/// # struct BorrowBook { book_id: String, reader: String }
35/// use architecturekit::{Decider, DomainError};
36///
37/// let borrow_book = Decider::new(|cmd: BorrowBook, book: Book| async move {
38/// if book.is_borrowed {
39/// return Err(DomainError::new(format!("book {} is already borrowed", cmd.book_id)));
40/// }
41///
42/// Ok(vec![BookEvent::Borrowed(BookBorrowed { reader: cmd.reader })])
43/// });
44/// ```
45///
46/// The types of the command, the state and the error come from the function,
47/// so that none of them has to be named. A decider that never refuses names
48/// its error type, as in `Decider::<_, _, DomainError>::new(…)`.
49///
50/// The function takes the command and the state rather than references to
51/// them, so that its future can be sent across threads, which a web server
52/// needs.
53pub struct Decider<C, S: State, D> {
54 decide: Box<Decide<C, S, D>>,
55}
56
57impl<C, S: State, D> Decider<C, S, D> {
58 /// Creates a decider from a function that decides on a command, given the
59 /// state of its subject.
60 #[must_use]
61 pub fn new<F, Fut>(decide: F) -> Self
62 where
63 F: Fn(C, S) -> Fut + Send + Sync + 'static,
64 Fut: Future<Output = Decision<S, D>> + Send + 'static,
65 {
66 Self {
67 decide: Box::new(move |command, state| Box::pin(decide(command, state))),
68 }
69 }
70
71 /// Decides on a command, given the state of its subject, with the function
72 /// the decider was created with, and returns what that function returns.
73 ///
74 /// # Errors
75 ///
76 /// Fails with the error of the function, if it refuses the command.
77 pub async fn decide(&self, command: C, state: S) -> Decision<S, D> {
78 (self.decide)(command, state).await
79 }
80}
81
82impl<C, S: State, D> fmt::Debug for Decider<C, S, D> {
83 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
84 formatter
85 .debug_struct("Decider")
86 .field("command", &any::type_name::<C>())
87 .field("state", &any::type_name::<S>())
88 .field("error", &any::type_name::<D>())
89 .finish_non_exhaustive()
90 }
91}