forked from serverlesstechnology/cqrs
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcqrs.rs
More file actions
189 lines (185 loc) · 7.19 KB
/
Copy pathcqrs.rs
File metadata and controls
189 lines (185 loc) · 7.19 KB
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
use std::collections::HashMap;
use crate::query::Query;
use crate::store::EventStore;
use crate::Aggregate;
use crate::{AggregateContext, AggregateError};
/// This is the base framework for applying commands to produce events.
///
/// In [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design) we require that
/// changes are made only after loading the entire `Aggregate` in order to ensure that the full
/// context is understood.
/// With event-sourcing this means:
/// 1. Loading all previous events for the aggregate instance.
/// 1. Applying these events, in order, to a new `Aggregate` in order to reach the correct state.
/// 1. Using the recreated `Aggregate` to handle an inbound `Command` producing events or an error
/// (see `handle` method in this trait).
/// 1. Persisting any generated events or roll back in the event of an error.
///
/// To manage these tasks we use a `CqrsFramework`.
///
pub struct CqrsFramework<A, ES>
where
A: Aggregate,
ES: EventStore<A>,
{
store: ES,
queries: Vec<Box<dyn Query<A>>>,
service: A::Services,
}
impl<A, ES> CqrsFramework<A, ES>
where
A: Aggregate,
ES: EventStore<A>,
{
/// Creates new framework for dispatching commands using the provided elements.
/// Takes an implementation of an `EventStore`, a vector of queries and a set of services
/// to be used within the command handler.
///
/// For a simple in-memory `EventStore` suitable for experimentation or testing see
/// [MemStore](mem_store/struct.MemStore.html).
///
/// ```rust
/// # use cqrs_es::doc::{MyAggregate, MyService};
/// use cqrs_es::CqrsFramework;
/// use cqrs_es::mem_store::MemStore;
///
/// let store = MemStore::<MyAggregate>::default();
/// let queries = vec![];
/// let service = MyService::default();
///
/// let cqrs = CqrsFramework::new(store, queries, service);
/// ```
/// For production uses a
/// [persistent event store](https://docs.rs/cqrs-es/latest/cqrs_es/persist/struct.PersistedEventStore.html)
/// using a backing database is needed, such as in the available persistence crates:
/// - [PostgreSQL](https://www.postgresql.org/) - [postgres-es](https://crates.io/crates/postgres-es)
/// - [MySQL](https://www.mysql.com/) - [mysql-es](https://crates.io/crates/mysql-es)
/// - [DynamoDb](https://aws.amazon.com/dynamodb/) - [dynamo-es](https://crates.io/crates/dynamo-es)
///
pub fn new(store: ES, queries: Vec<Box<dyn Query<A>>>, service: A::Services) -> Self
where
A: Aggregate,
ES: EventStore<A>,
{
Self {
store,
queries,
service,
}
}
/// Appends an additional query to the framework.
/// ```rust
/// # use cqrs_es::doc::{MyAggregate, MyQuery, MyService};
/// use cqrs_es::CqrsFramework;
/// use cqrs_es::mem_store::MemStore;
///
/// let store = MemStore::<MyAggregate>::default();
/// let queries = vec![];
/// let service = MyService::default();
///
/// let cqrs = CqrsFramework::new(store, queries, service)
/// .append_query(Box::new(MyQuery::default()));
/// ```
pub fn append_query(self, query: Box<dyn Query<A>>) -> Self
where
A: Aggregate,
ES: EventStore<A>,
{
let mut queries = self.queries;
queries.push(query);
Self {
store: self.store,
queries,
service: self.service,
}
}
/// This applies a command to an aggregate. Executing a command
/// in this way is the only way to make changes to
/// the state of an aggregate in CQRS.
///
/// An error while processing will result in no events committed and
/// an [`AggregateError`](https://docs.rs/cqrs-es/latest/cqrs_es/enum.AggregateError.html)
/// being returned.
///
/// If successful the events produced will be persisted in the backing `EventStore`
/// before being applied to any configured `QueryProcessor`s.
///
/// ```
/// # use cqrs_es::{AggregateError, CqrsFramework};
/// # use cqrs_es::doc::{MyAggregate, MyCommands, MyUserError};
/// # use cqrs_es::mem_store::MemStore;
/// # use std::collections::HashMap;
/// # use chrono;
/// type MyFramework = CqrsFramework<MyAggregate,MemStore<MyAggregate>>;
///
/// async fn do_something(cqrs: MyFramework) -> Result<(),AggregateError<MyUserError>> {
/// let command = MyCommands::DoSomething;
///
/// cqrs.execute("agg-id-F39A0C", command).await
/// }
/// ```
pub async fn execute(
&self,
aggregate_id: &str,
command: A::Command,
) -> Result<(), AggregateError<A::Error>> {
self.execute_with_metadata(aggregate_id, command, HashMap::new())
.await
}
/// This applies a command to an aggregate. Executing a command
/// in this way is the only way to make changes to
/// the state of an aggregate in CQRS.
///
/// A `Hashmap<String,String>` is supplied with any contextual information that should be
/// associated with this change. This metadata will be attached to any produced events and is
/// meant to assist in debugging and auditing. Common information might include:
/// - time of commit
/// - user making the change
/// - application version
///
/// An error while processing will result in no events committed and
/// an [`AggregateError`](https://docs.rs/cqrs-es/latest/cqrs_es/enum.AggregateError.html)
/// being returned.
///
/// If successful the events produced will be persisted in the backing `EventStore`
/// before being applied to any configured `QueryProcessor`s.
///
/// ```
/// # use cqrs_es::{AggregateError, CqrsFramework};
/// # use cqrs_es::doc::{MyAggregate, MyCommands, MyUserError};
/// # use cqrs_es::mem_store::MemStore;
/// # use std::collections::HashMap;
/// # use chrono;
/// type MyFramework = CqrsFramework<MyAggregate,MemStore<MyAggregate>>;
///
/// async fn do_something(cqrs: MyFramework) -> Result<(),AggregateError<MyUserError>> {
/// let command = MyCommands::DoSomething;
/// let mut metadata = HashMap::new();
/// metadata.insert("time".to_string(), chrono::Utc::now().to_rfc3339());
///
/// cqrs.execute_with_metadata("agg-id-F39A0C", command, metadata).await
/// }
/// ```
pub async fn execute_with_metadata(
&self,
aggregate_id: &str,
command: A::Command,
metadata: HashMap<String, String>,
) -> Result<(), AggregateError<A::Error>> {
let aggregate_context = self.store.load_aggregate(aggregate_id).await?;
let aggregate = aggregate_context.aggregate();
let resultant_events = match aggregate.handle(command, &self.service).await {
Ok(events) => events,
Err(err) => return Err(AggregateError::UserError(err)),
};
let committed_events = self
.store
.commit(resultant_events, aggregate_context, metadata)
.await?;
for processor in &self.queries {
let dispatch_events = committed_events.as_slice();
processor.dispatch(aggregate_id, dispatch_events).await;
}
Ok(())
}
}