如何处理事件来源中的副作用?


14

假设我们要为金融应用程序实现一个小型安全子系统,该子系统将在检测到异常模式时通过电子邮件向用户发出警告。对于此示例,该模式将包括所描述的三个事务。安全子系统可以从队列中读取主系统中的事件。

我想得到的是一个警报,它是系统中发生的事件的直接结果,而没有中间模式来模拟模式的当前状态。

  1. 监控已激活
  2. 交易已处理
  3. 交易已处理
  4. 交易已处理
  5. 警报已触发(ID:123)
  6. 已发送警报电子邮件(ID:123)
  7. 交易已处理

考虑到这一点,我认为事件源可以在这里很好地应用,尽管我有一个没有明确答案的问题。在示例中触发的警报具有明显的副作用,需要发送电子邮件,这种情况只能发生一次。因此,在重放聚合的所有事件时不应发生这种情况。

在某种程度上,我看到需要发送的电子邮件类似于查询方生成的实现,这在CQRS / Event采购文献中已经见过很多次了,尽管差别不大。

在此文献中,查询端是由事件处理程序构建的,该事件处理程序可以在给定点生成状态的实现,从而再次读取所有事件。但是,在这种情况下,由于前面解释的原因,不能完全像那样完成。每个状态都是瞬态的想法在这里并不适用。我们需要记录警报发送到某个地方的事实。

对于我来说,一个简单的解决方案是使用其他表或结构,在其中保留先前触发的警报的记录。由于我们具有ID,因此我们可以检查之前是否发布了具有相同ID的警报。拥有此信息将使SendAlertCommand成为幂等。可以发出几个命令,但副作用只会发生一次。

即使考虑到该解决方案,我也不知道这是否暗示此体系结构对此问题有问题。

  • 我的方法正确吗?
  • 在哪里可以找到更多有关此的信息?

奇怪的是我还没有找到更多有关此的信息。也许我一直在使用错误的措词。

非常感谢!

Answers:


12

如何处理事件来源中的副作用?

简短版本:域模型不具有副作用。它跟踪它们。副作用是通过连接边界的端口执行的。发送电子邮件后,您会将确认发送回域模型。

这意味着电子邮件是更新事件流的事务之外发送

确切地说,在外面是一个品味问题。

因此,从概念上讲,您会有一系列事件

EmailPrepared(id:123)
EmailPrepared(id:456)
EmailPrepared(id:789)
EmailDelivered(id:456)
EmailDelivered(id:789)

从此流中,您可以创建折叠

{
    deliveredMail : [ 456, 789 ],
    undeliveredMail : [123]
}

折页会告诉您哪些电子邮件未被确认,因此您可以再次发送:

undeliveredMail.each ( mail -> {
    send(mail);
    dispatch( new EmailDelivered.from(mail) );
}     

实际上,这是一个分为两个阶段的提交:您要在现实世界中修改SMTP,然后再更新模型。

上面的模式为您提供了至少一次的交付模型。如果您最多只需要一次

undeliveredMail.each ( mail -> {
    commit( new EmailDelivered.from(mail) );
    send(mail);
}     

在使EmailPrepared持久与实际发送电子邮件之间存在交易障碍。在发送电子邮件和使EmailDelivered持久之间还存在交易障碍。

Udi Dahan的《可靠的分布式交易消息》可能是一个很好的起点。


2

您需要将“状态更改事件”与“操作”分开

一个状态改变事件是改变对象的状态的事件。这些是您存储并重放的内容。

一个动作是什么物体确实给其他的事情。这些不存储为事件来源的一部分。

一种方法是使用事件处理程序,您可以根据是否要运行操作来进行连接。

public class Monitor
{
    public EventHander SoundAlarm;
    public void MonitorEvent(Event e)
    {
        this.eventcount ++;
        if(this.eventcount > 10)
        {
             this.state = "ALARM!";
             if(SoundAlarm != null) { SoundAlarm();}
        }
    }
}

现在在我的监控服务中,我可以

public void MonitorServer()
{
    var m = new Monitor(events); //11 events
    //alarm has not been sounded because the event handler wasn't wired up
    //but the internal state is correctly set to "ALARM!"
    m.SoundAlarm += this.SendAlarmEmail;
    m.MonitorEvent(e); //email is sent
}

如果您需要记录发送的电子邮件,则可以作为SendAlarmEmail的一部分进行记录。但它们不是事件源中的事件

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.