1mod support;
12
13use ddd_toolkit::{
14 AggregateRoot as _, CommandHandler, LifecycleCallback, LifecycleCompletionError,
15 LifecycleContext, LifecycleOperationError, Policy, QueryHandler, Repository, Specification,
16};
17use std::convert::Infallible;
18use support::{Order, Placed, Publisher, Store};
19
20struct Positive;
23impl Specification<u32> for Positive {
24 type Error = Infallible;
25 fn is_satisfied_by(&self, amount: &u32) -> Result<bool, Infallible> {
26 Ok(*amount > 0)
27 }
28}
29struct Limit;
32impl Policy<u32> for Limit {
33 type Error = Error;
34 fn check(&self, total: &u32) -> Result<(), Error> {
35 if *total <= 5000 {
36 Ok(())
37 } else {
38 Err(Error::Rejected)
39 }
40 }
41}
42
43#[derive(Debug, PartialEq)]
44enum Error {
45 NotFound,
46 Rejected,
47 Operation(LifecycleOperationError<&'static str>),
48 Completion(LifecycleCompletionError),
49}
50impl From<LifecycleOperationError<Infallible>> for Error {
51 fn from(error: LifecycleOperationError<Infallible>) -> Self {
52 match error {
53 LifecycleOperationError::Adapter(never) => match never {},
54 LifecycleOperationError::AlreadySaved => {
55 Self::Operation(LifecycleOperationError::AlreadySaved)
56 }
57 LifecycleOperationError::AlreadyDispatched => {
58 Self::Operation(LifecycleOperationError::AlreadyDispatched)
59 }
60 }
61 }
62}
63impl From<LifecycleOperationError<&'static str>> for Error {
64 fn from(error: LifecycleOperationError<&'static str>) -> Self {
65 Self::Operation(error)
66 }
67}
68impl From<LifecycleCompletionError> for Error {
69 fn from(error: LifecycleCompletionError) -> Self {
70 Self::Completion(error)
71 }
72}
73
74struct AddToOrder {
75 store: Store,
76 publisher: Publisher,
77}
78impl CommandHandler for AddToOrder {
79 type Command = u32;
80 type Aggregate = Order;
81 type Repository = Store;
82 type Publisher = Publisher;
83 type Output = ();
84 type Error = Error;
85 fn handle<F>(&mut self, id: &u64, amount: u32, callback: F) -> Result<(), Error>
86 where
87 F: for<'a> LifecycleCallback<'a, Order, Store, Publisher, Output = (), Error = Error>,
88 {
89 let mut order = self.store.find_by_id(id).unwrap().ok_or(Error::NotFound)?;
90 if !Positive.is_satisfied_by(&amount).unwrap() {
91 return Err(Error::Rejected);
92 }
93 let total = order.total.checked_add(amount).ok_or(Error::Rejected)?;
94 Limit.check(&total)?;
95 order.total = total;
96 order.record_event(Placed(order.id));
97 let mut context = LifecycleContext::new(&mut order, &mut self.store, &mut self.publisher);
98 callback.call(&mut context)?;
99 context.complete()?;
100 Ok(())
101 }
102}
103struct SaveThenDispatch;
106impl<'storage> LifecycleCallback<'storage, Order, Store, Publisher> for SaveThenDispatch {
107 type Output = ();
108 type Error = Error;
109 fn call<'callback>(
110 self,
111 context: &'callback mut LifecycleContext<'storage, Order, Store, Publisher>,
112 ) -> Result<(), Error>
113 where
114 'storage: 'callback,
115 {
116 context.save()?;
117 context.dispatch()?;
118 Ok(())
119 }
120}
121struct Total<'a>(&'a Store);
122impl QueryHandler for Total<'_> {
123 type Query = u64;
124 type Result = u32;
125 type Error = Error;
126 fn handle(&self, id: u64) -> Result<u32, Error> {
127 self.0
128 .find_by_id(&id)
129 .unwrap()
130 .map(|order| order.total)
131 .ok_or(Error::NotFound)
132 }
133}
134
135fn main() {
136 let mut handler = AddToOrder {
137 store: Store::default(),
138 publisher: Publisher::default(),
139 };
140 handler
141 .store
142 .save(&Order {
143 id: 1,
144 total: 4000,
145 events: vec![],
146 })
147 .unwrap();
148 assert_eq!(
149 handler.handle(&1, 0, SaveThenDispatch),
150 Err(Error::Rejected)
151 );
152 assert_eq!(
153 handler.handle(&1, 1001, SaveThenDispatch),
154 Err(Error::Rejected)
155 );
156 assert_eq!(Total(&handler.store).handle(1), Ok(4000));
157 assert!(handler.publisher.accepted.is_empty());
158 handler.handle(&1, 200, SaveThenDispatch).unwrap();
159 assert_eq!(Total(&handler.store).handle(1), Ok(4200));
160 assert_eq!(handler.publisher.accepted, vec![Placed(1)]);
161 println!("cqrs: rejected invalid commands; query total=4200");
162}