通过重放 EventStore 中的事件,特别是当事件可能触发其他事件发生时,我正在努力弄清楚在重建模型时应该发生什么。
例如,应该将购买了 10 次的用户提升为首选客户,并收到一封电子邮件,向他们提供某些促销活动。
我们显然不希望每次为该用户重建模型时都发送电子邮件,但是当我们重播第 10 次时如何阻止这种情况发生PurchaseMadeEvent
?
通过重放 EventStore 中的事件,特别是当事件可能触发其他事件发生时,我正在努力弄清楚在重建模型时应该发生什么。
例如,应该将购买了 10 次的用户提升为首选客户,并收到一封电子邮件,向他们提供某些促销活动。
我们显然不希望每次为该用户重建模型时都发送电子邮件,但是当我们重播第 10 次时如何阻止这种情况发生PurchaseMadeEvent
?
事件链可能非常棘手并且很容易失控,所以我会尽可能避免它。例如,在您描述的场景中,我会提出一个UserPromotedEvent
(甚至可能使用PromoteUserCommand
),但是我不会将电子邮件的实际/物理发送视为我的域的一部分。相反,我会创建额外的处理程序/非规范化器,UserPromotedEvent
这很可能会注册发送电子邮件的需要以及一些额外的检查。之后,另一个进程将收集尚未处理的电子邮件的信息并发送它们。这种方法将缓解无法完全访问/可扩展的电子邮件网关可能出现的问题。
更一般地说 - 事件链接的需求通常表明您应该考虑为流程实现Saga。
您不应该从事件处理程序引发事件 - 只是不要这样做!您应该改用sagas。
在您的情况下,saga 订阅PurchaseMadeEvent
并发出PromoteCustomer
命令,这会导致发生CustomerPromoted
事件。同样,还有另一个 saga 订阅CustomerPromoted
事件并发送SendEmailToPromotedCustomer
命令。当您重播事件时 - 只是不要为CustomerPromoted
事件订阅 saga。
这就是命令和事件之间的区别。理解它很重要。事件说明已经发生的事情,命令说明将要发生的事情。
当您重放事件时,您并没有重放伴随生成这些事件的所有域逻辑。通常在你的领域方法中你会引发一个事件;然后该事件的引发应该更新该域对象的整体状态。
例如:
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;
}
}
在此示例中,当我的事件被重放时,OnPurchaseMade
将执行事件处理程序,而不是域对象构造函数。与PurchaseUpdatedEvent
- 它的事件处理程序将被执行,而不是引发事件的域方法相同。
该事件包含更新域模型(并将更新应用于读取模型)所需的一切。被执行的领域方法让你达到可以引发事件的地步。
我希望这有帮助。如果我需要提供更多信息,请告诉我。
祝你好运!!