I'm struggling to get my head around what should happen when rebuilding the model by replaying events from the EventStore, in particular when events may trigger other events to happen.
For example, a user who has made 10 purchases should be promoted to a preferred customer and receive an email offering them certain promotions.
We clearly don't want the email to be sent every time we rebuild the model for that user, but how do we stop this from happening when we replay our 10th PurchaseMadeEvent
?
When you replay events, you're not replaying all of the domain logic that went along with generating those events. Usually in your domain method you'll raise an event; the raising of that event then should update the overall state of that domain object.
For example:
In this example, when my events are replayed, the
OnPurchaseMade
event handler will get executed, not the domain object constructor. Same with thePurchaseUpdatedEvent
- it's event handler will get executed, not the domain method that raised the event.The event contains everything that you need to update the domain model (and apply the updates to the read model). The domain methods that get executed get you to the point that an event can be raised.
I hope this helps. Let me know if I need to provide more information.
Good luck!!
You should not raise event from event handler - just don't do it! You should use sagas instead.
In your case, saga subscribes for
PurchaseMadeEvent
and issuesPromoteCustomer
COMMAND, which causes to happenCustomerPromoted
event. Again, there is another saga that subscribes forCustomerPromoted
event and sendsSendEmailToPromotedCustomer
command. When you are replaying events - just don't subscribe saga forCustomerPromoted
event.This is all about the difference between command and event. It is important to understand it. Events tell what already has happened, commands tell what will happen.
Events chaining can be very tricky and easily run out of control, so I'd avoid it as much as possible. For example in the scenario you're describing I'd raise a
UserPromotedEvent
(maybe even using thePromoteUserCommand
), however I wouldn't consider actual/physical sending of an email as part of my domain. Instead I would create additional handler/denormalizer forUserPromotedEvent
that would register the need of sending the email with some additional checks quite possibly. After that another process would gather information of not yet processed emails and send them. This approach would mitigate the problems that might occur with not fully accessible/scalable email gateway.In more general - the need of events chaining very often indicates that you should consider implementing a Saga for the process.