【问题标题】:CQRS - When to send confirmation message?CQRS - 何时发送确认消息?
【发布时间】:2012-06-13 13:34:02
【问题描述】:

示例:业务规则规定客户在下订单后应收到确认消息(电子邮件或类似信息)。

假设NewOrderRegisteredEvent 从域中分派,并由发送确认消息的事件侦听器拾取。完成后,其他一些事件处理程序会抛出异常或出现其他问题,并且工作单元会回滚。我们现在已经向用户发送了一条回滚的确认消息。

解决此类问题的“cqrs”方法是什么?在提交了一个工作单元后您想要做某事?另一个复杂的因素是重播事件。每当我重播记录的事件以构建新的视图/投影时,我不希望重新发送旧的确认消息。

迄今为止我最好的理论:我刚刚开始研究 cqrs 的迷人世界,想知道这是否会作为传奇来实现?如果 saga 就像一个状态机,每个转换只能发生一次,那么我想这会解决这个问题吗?我只是很难想象这将如何与命令总线和域事件结合在一起。..

【问题讨论】:

  • 使“发送电子邮件”任务成为消息。如果 uow 回滚,则消息的分派(到持久存储)也会回滚。其他东西会接收消息并处理电子邮件。顺便说一句,与 cqrs 无关。普通的分布式计算常识。
  • 至于事件回放,绝不会引发这种行为。如果你的暗示。确实,你做错了。
  • 这是有道理的,但我在 cqrs 上下文中对此感兴趣。什么是“消息”?谁/什么会在什么时候拿起它?最好利用事件存储来避免引入另一种存储机制来跟踪传出通信。
  • 您的 eventstore 就是您的队列。然而,实际发送电子邮件的代码需要通过基于拉取或推送的机制/技术来通知。
  • @YvesReynhout:这实际上是我几天前才意识到的。我喜欢的是它可以摆脱事件存储和事件消费者之间的任何消息总线。这确实使每个消费者负责跟踪它已处理的内容,但这是摆脱消息总线的一小笔代价!

标签: domain-driven-design cqrs


【解决方案1】:
  1. 只有在事务完成后才会发生事件。如果出现任何问题并且有回滚,那么从外部角度来看,该事件不会发生。因此,它根本不应该发布。虽然必要时可以发布OrderRegistrationFailed 事件。

  2. 除非命令已成功执行,否则您不会希望发送邮件。

    首先,命令处理程序(如另一个答案中提出的)会出现在错误位置的几个原因:在某些情况下,命令处理程序无法判断命令最终是否会成功。让命令处理程序调用邮件发送也会将流程知识放入命令处理程序中,这会破坏 SRM 并将业务规则与应用层紧密耦合。

    邮件应该事后发送,即从事件处理程序发送。

    为防止此处理程序在重放期间触发,您可以不注册它。这与您测试应用程序的方式类似。您只需注册您实际需要的处理程序。

    • 生产系统 -> 注册所有事件处理程序
    • 测试 -> 仅注册测试的事件处理程序
    • 重播 -> 仅注册投影/非规范化处理程序

另一个 - 甚至更松散耦合,虽然有点复杂 - 可能是让 Saga 处理 NewOrderRegisteredEvent 并向适当的有界上下文发出 SendMail 命令(感谢 Yves Reynhout 指出这在问题的 cmets 中)。

【讨论】:

  • 这是我在 stackoverflow 上得到的最佳答案之一!那么域事件将在某处缓冲,并且仅在事件成功写入事件日志后才在域外发布(到投影/反规范化器)?我猜想写入事件存储发生在由命令处理程序/拦截器初始化的事务中(通常)?
  • 基本上是的,一个或多个事件的存储(在事件日志或事件存储中,注意不要混淆两者)将是事务的最后一部分。只有在成功存储之后,它/它们才会被调度和处理,甚至可能是异步的。
  • 事件日志和存储有什么区别?我很确定在阅读有关 cqrs 时,我已经看到它们可以互换使用..
  • 事件日志只是存储事件以供参考和回放的表或文件。领域模型的持久性仍然可以是基于状态的,例如在关系数据库中。一个可能的进一步步骤是不从状态而是从事件本身存储和再现域模型。这就是所谓的事件溯源。在这种情况下,我们将谈论事件存储而不是事件日志。 (当然,即使从技术上讲,事件日志也是一个存储。混淆通常发生在仅使用事件日志和使用事件源模式之间)。
  • 如果Saga在调用命令后进行事件可以吗?
【解决方案2】:

有两种可能的解决方案

1) 事件的发布和事件的处理(即电子邮件)是单个事务的一部分。在这种情况下,您的事务框架会为您处理它。如果电子邮件失败,则回滚事件。您可能会重试该命令。这在概念上是干净的,很容易思考。在每个有话要说的人都发表意见之前,任何活动都不会完成发布。但实际上,这可能很痛苦,因为它通常涉及分布式事务。这些都很难得到。您的电子邮件客户端可以注册与保存您的事件的数据库相同的事务吗?

2) 事件的发布是事务性的,但事件处理程序各自以自己的方式处理事务。发送电子邮件的事件处理程序可以跟踪它看到的事件。如果它崩溃了,它会请求旧事件并处理它们。如果人们丢失或重复电子邮件,您可以做出一个商业决策,看看会有多大的影响。 (对于与金钱相关的交易,答案可能是你不应该允许它。)

解决方案 (2) 通常是您在 DDD/CQRS 圈子中看到的推广,因为它是更松散耦合的解决方案。解决方案 (1) 在事件存储和投影位于单个数据库中且投影不经常更改的小型系统中非常实用。解决方案 (2) 允许多种事件处理程序以它们自己的方式工作。解决方案 (1) 可能会导致许多不重叠的关注点变得纠缠不清。在这种情况下,您的订单业务规则要等到电子邮件中发生的许多奇怪的事情都得到处理后才能完成。一方面,它可能会减慢你的速度。

如果发送电子邮件比“看到事件,发送电子邮件”更有趣,那么您是对的,您可能手头有一个传奇或工作流程。大型运营中的电子邮件通常本身就是一个复杂的系统,您不太可能需要大量实施。您只需要确保将您的电子邮件放入某种请求队列(使用方法(2)),并且电子邮件系统可能会进行重试/批处理/避免垃圾邮件/通宵工作/等等。

【讨论】:

  • 感谢您的长回答!分布式事务是一种痛苦。关于第二个解决方案,这是否涉及引入一种新的存储机制来跟踪它已发送的电子邮件,或者通常也利用事件存储来实现这一点?我发现事件处理程序发布自己的事件存在一些潜在问题。尤其是在播放期间,“已发送电子邮件 x”事件将在事件首先触发电子邮件之后发生。
  • 如果您想要真正的事务性电子邮件发送,那么您必须以某种方式处理它。也许通过保存您已成功发送到磁盘的电子邮件的身份。但是,如果您将需要的消息写入某个队列,大多数商业级电子邮件操作是否不能保证送达?
  • 真正的事务性电子邮件发送现在已经超出了范围。我只是好奇这个问题在 cqrs 中是如何处理的,尤其是与事件重放结合使用。但是,是的,将电子邮件放在队列中并将关联 ID 存储在事件存储中将大大有助于保证交付。
猜你喜欢
  • 2015-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-14
  • 1970-01-01
  • 1970-01-01
  • 2021-11-13
相关资源
最近更新 更多