事件溯源:将业务逻辑放在哪里
Event Sourcing: where to put business logic
我可以想到在事件源系统中放置域逻辑的两个地方,任何一个都有缺点。
- 在聚合的事件处理程序中,在创建事件后在本地调用。 (这是我在大多数 examples 中看到的,尽管其中大多数的逻辑都非常简单)
问题:存储在事件存储中并发布给订阅者的事件不包括此处理后的数据,因此在投影上必须对事件应用相同的逻辑。
- 创建活动之前。现在,处理后的数据可以存储在事件中,投影无需了解任何业务逻辑。 (我没有在示例中看到这种方法)
问题:在这种情况下,尽管事件仅包含可能导致信息丢失的已处理数据。
更糟糕的是:我还失去了通过重放事件来纠正错误业务逻辑的可能性,因为事件数据已经计算完毕。
示例:根据一些数据计算指标。
要么我必须计算两次度量(一次在域模型中,一次在投影中)
或者我必须在发送事件并将其包含在那里之前计算它。
我想你的聚合根中有一些状态。这应该包含有关您的业务的逻辑。所以它应该有足够的数据来创建基于命令的事件。
此事件的消费者(查询模型)进行计算。因此,如果目的是计算平均值,它必须设法以给定的方式存储自己。
我做了类似的事情。曾经是业务逻辑的一部分,所以在聚合中,曾经在查询模型中,因为它不是业务逻辑的一部分,更多的是度量计算。
不要害怕将数据存储在多个地方。一致性的责任应该委托给事件分发,而不是您的业务逻辑。
控制流程通常是这样的:
- 命令被发送到命令处理程序并且命令中的属性被预先验证,例如身份指向现有实体并且所有强制信息都存在并且格式正确
- 命令处理程序从存储库中检索聚合(通过读取事件流,但这并不重要)并根据此命令需要完成的操作调用聚合方法
聚合方法必须确保它们的参数和聚合状态相互允许执行操作。
聚合方法然后创建一个事件并调用此When
或Apply
方法来处理事件
事件处理程序仅改变聚合状态,那里没有逻辑!
- 然后控制流返回到命令处理程序,并且它将所有新事件保存在存储区中
进一步的行动与预测有关。
在应用事件之前将不变保护(即业务逻辑)放入聚合事件中的原因是因为当事件生成时就没有回头路可走了。这件事已经发生了。您不能拒绝申请一个事件。考虑在从事件流(从存储库读取)中恢复聚合时重播事件,如果有一天您决定在那里有一个 if-throw
组合,这将如何工作?
所以,简而言之:
- 发送命令前应用初始逻辑
- 命令处理程序中有一些额外的逻辑
- 聚合方法中的聚合保护(很可能也在命令处理程序中)
- 事件处理程序中没有逻辑,只有状态变化
没有人说过事件溯源可以帮助您解决计算中的问题。为了建立额外的安全网,您可能想要保存命令,但随后您将不得不发出补偿事件或截断流,这并不是您真正想要做的。
我可以想到在事件源系统中放置域逻辑的两个地方,任何一个都有缺点。
- 在聚合的事件处理程序中,在创建事件后在本地调用。 (这是我在大多数 examples 中看到的,尽管其中大多数的逻辑都非常简单)
问题:存储在事件存储中并发布给订阅者的事件不包括此处理后的数据,因此在投影上必须对事件应用相同的逻辑。 - 创建活动之前。现在,处理后的数据可以存储在事件中,投影无需了解任何业务逻辑。 (我没有在示例中看到这种方法)
问题:在这种情况下,尽管事件仅包含可能导致信息丢失的已处理数据。
更糟糕的是:我还失去了通过重放事件来纠正错误业务逻辑的可能性,因为事件数据已经计算完毕。
示例:根据一些数据计算指标。
要么我必须计算两次度量(一次在域模型中,一次在投影中)
或者我必须在发送事件并将其包含在那里之前计算它。
我想你的聚合根中有一些状态。这应该包含有关您的业务的逻辑。所以它应该有足够的数据来创建基于命令的事件。
此事件的消费者(查询模型)进行计算。因此,如果目的是计算平均值,它必须设法以给定的方式存储自己。
我做了类似的事情。曾经是业务逻辑的一部分,所以在聚合中,曾经在查询模型中,因为它不是业务逻辑的一部分,更多的是度量计算。
不要害怕将数据存储在多个地方。一致性的责任应该委托给事件分发,而不是您的业务逻辑。
控制流程通常是这样的:
- 命令被发送到命令处理程序并且命令中的属性被预先验证,例如身份指向现有实体并且所有强制信息都存在并且格式正确
- 命令处理程序从存储库中检索聚合(通过读取事件流,但这并不重要)并根据此命令需要完成的操作调用聚合方法
聚合方法必须确保它们的参数和聚合状态相互允许执行操作。
聚合方法然后创建一个事件并调用此
When
或Apply
方法来处理事件事件处理程序仅改变聚合状态,那里没有逻辑!
- 然后控制流返回到命令处理程序,并且它将所有新事件保存在存储区中
进一步的行动与预测有关。
在应用事件之前将不变保护(即业务逻辑)放入聚合事件中的原因是因为当事件生成时就没有回头路可走了。这件事已经发生了。您不能拒绝申请一个事件。考虑在从事件流(从存储库读取)中恢复聚合时重播事件,如果有一天您决定在那里有一个 if-throw
组合,这将如何工作?
所以,简而言之:
- 发送命令前应用初始逻辑
- 命令处理程序中有一些额外的逻辑
- 聚合方法中的聚合保护(很可能也在命令处理程序中)
- 事件处理程序中没有逻辑,只有状态变化
没有人说过事件溯源可以帮助您解决计算中的问题。为了建立额外的安全网,您可能想要保存命令,但随后您将不得不发出补偿事件或截断流,这并不是您真正想要做的。