Update aggregate in real time as events are added
Currently events are returned as a vector collection of all the events that "should" be emitted, but state change from earlier events may not be reflect in the later events (or possible events that are not fired). This is a known flaw in the current "simple" design of this tool..
Correctly, as an event is "added" within the aggregate, the aggregate should immediately be updated to reflect the current state. This will require a fundamental change in the model and a major version bump.
Problem
Currently, events are only applied to the aggregate after they are committed, the results are then only seen after the next command is processed. For events that are only produced based on aggregate state, complex and repetitive handling logic is needed.
For example, if a simple aggregate has the commands and events:
| Commands | Events |
|---|---|
| FundAccount | AccountFunded |
| VerifyUser | UserVerified |
| AccountReady |
Where the AccountReady event should be fired only after the account is funded
and user verified then the command handler logic may look something like:
let mut result: Vec<AccountEvents> = Default::default();
match command {
FundAccount => {
result.append(AccountFunded);
if self.user_verified && !self.ready {
result.append(AccountReady);
}
}
VerifyUser => {
result.append(UserVerified);
if self.account_funded && !self.ready {
result.append(AccountReady);
}
}
}
return Ok(result);
Note the redundant checks to fire the AccountReady event.
This is tolerable in the simple aggregate example presented here but becomes exponentially
more complex as domain logic grows.
(Internally, this has been seen as a problem in nearly every production system using this package).
Solution
Rather than add events to a vector that is then returned from the command handler, each will be processed individually. An event sink will be used to write these events as they are produced and retain a copy for committing if the method is successful.
Our example logic will then look something like:
match command {
FundAccount => {
result.append(AccountFunded);
}
VerifyUser => {
result.append(UserVerified);
}
}
if self.user_verified && self.account_funded && !self.ready {
result.append(AccountReady);
}
return Ok(());