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
?
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 the PromoteUserCommand
), however I wouldn't consider actual/physical sending of an email as part of my domain. Instead I would create additional handler/denormalizer for UserPromotedEvent
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.
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 issues PromoteCustomer
COMMAND, which causes to happen CustomerPromoted
event. Again, there is another saga that subscribes for CustomerPromoted
event and sends SendEmailToPromotedCustomer
command. When you are replaying events - just don't subscribe saga for CustomerPromoted
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.
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:
public class Purchase {
private int _id;
private string _name;
private string _address;
private double _amount;
public Purchase(int id, string name, string address) {
//do some business rule checking to determine if event is raised
//perhaps send an email or do some logging
//etc.
if (should_i_raise_event) {
ApplyEvent(new PurchaseMadeEvent() {
ID = id,
Name = name,
Address = address
});
}
}
public UpdatePurchase(int id, double amount) {
//more checking to see if event is to be raised
if (should_i_raise_event) {
ApplyEvent(new PurchaseUpdatedEvent() {
ID = id,
Amount = amount
});
}
}
protected void OnPurchaseMade(PurchaseMadeEvent e){
_id = e.ID;
_name = e.Name;
_address = e.Address;
}
protected void OnPurchaseUpdated(PurchaseUpdatedEvent e){
_id = e.ID;
_amount = e.Amount;
}
}
In this example, when my events are replayed, the OnPurchaseMade
event handler will get executed, not the domain object constructor. Same with the PurchaseUpdatedEvent
- 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!!