Phi*_*ght 3 cqrs event-sourcing
我可以看到如何使用CQRS + ES模式严格按时间顺序执行命令。具有多个并行执行的命令将显着提高系统的吞吐量,并利用我在服务器上拥有的所有内核。但这肯定会带来并发问题吗?
举一个简单的例子,两个用户更新客户名称并同时提交命令。并行地,两个进程重播先前的事件以使聚合进入最新状态,然后两者都确定一切均有效。然后两者都保存一个新的更新事件。哪个保存最后一个似乎都是赢家,因为任何将来的重放都会导致最后一次更新成为客户的名字。
如果命令的结果是向客户发送电子邮件该怎么办。现在,我们发送两封电子邮件,而不是预期的一封。如果命令导致创建使用客户属性附加到客户的新聚合,该怎么办,我们最终创建了两个新聚合,但一个是僵尸,因为它从未被客户引用,因为另一个聚合已保存为而是参考。
感觉您需要实现自己的事务锁定功能,以确保您永远不会被覆盖,这是我宁愿避免的复杂解决方案!
更新:
我的例子有点太简单了。想象一下,我们有一个发票和进入发票的项目条目。从Invoice添加/更改/删除项目后,我们需要找到新的总计并将其设置在Invoice.Total属性字段中。
如果并行地两个用户都添加了一个新Item,那么在没有显式锁定机制的情况下总数将是错误的。双方重新创建的发票总量和现有列表项的孩子聚集该发票。并行创建两个新项,然后将新总数合计并将其设置为Invoice.Total属性。
然后两者都保存这些动作的事件。但是现在我们遇到了一个错误,因为最后执行的命令中的总和将不包含先完成的实例中的新项。这与悲观/乐观并发无关,而与事务锁定有关。
我可以看到如何使用CQRS + ES模式严格按时间顺序执行命令。具有多个并行执行的命令将显着提高系统的吞吐量,并利用我在服务器上拥有的所有内核。但这肯定会带来并发问题吗?
从CQRS+ES角度来看,没有并发问题。任何良好的Event store实现都有一个Aggregate版本约束,Aggregate例如使用乐观锁定来防止在同一事件上添加并发事件。
更新:CQRS+ES喜欢并欢迎并发命令。
如果命令的结果是向客户发送电子邮件该怎么办。现在,我们发送两封电子邮件,而不是预期的一封。
期待谁?同样,这不是CQRS+ES问题。这是业务设计问题。在这种特殊情况下,您应该设计另一个有界上下文Aggregate,用于处理向客户端发送电子邮件的业务不变性,以确保将与某个主题(即用户名更改)有关的多封电子邮件分组为一个,最后一个具有更高的优先级。
如果命令导致创建使用客户属性附加到客户的新聚合,该怎么办,我们最终创建了两个新聚合,但一个是僵尸,因为它从未被客户引用,因为另一个聚合已保存为而是参考。
同样,这取决于您的业务。从CQRS + ES的角度来看,这没有问题。事件存储擅长处理大量数据(这是因为事件存储仅是追加的持久性)。僵尸聚集是无害的。它仅占用事件存储中的某些事件。在投影(读取的模型)中,没有疼痛,因为它不存在。
感觉您需要实现自己的事务锁定功能,以确保您永远不会被覆盖,这是我宁愿避免的复杂解决方案!
无论使用CQRS + ES,都将遇到此问题。如果这是一项业务要求,则无论如何都必须这样做。
对于两个用户同时更改用户名的情况,您可以做一些附加检查,并通知丢失的用户其他用户已经修改了用户名,甚至可能显示什么用户名并让他决定下一步做什么:重试或取消。
更新后:
我的例子有点太简单了。假设我们有一个发票和一个进入发票的项目条目。从Invoice添加/更改/删除项目后,我们需要找到新的总计并将其设置在Invoice.Total属性字段中。
如果并行地两个用户都添加了一个新Item,那么在没有显式锁定机制的情况下总数将是错误的。两者都会重新创建发票汇总和该发票的现有子项汇总列表。并行创建两个新项,然后将新总数合计并将其设置为Invoice.Total属性。
然后两者都保存这些动作的事件。但是现在我们遇到了一个错误,因为最后执行的命令中的总和将不包含先完成的实例中的新项。这与悲观/乐观并发无关,但与事务锁定有关
我认为您必须了解其Event sourcing工作原理:让我们假设两个用户都在他们前面Invoice(实际上是Cart他们看到的,而不是Invoice; 放置Invoice在之后生成的;Order但是为了简单起见,我们假设)。假设他们看到2个项目,并且每个人都想添加一个新项目。他们两个都发起一个命令:AddAnItemToInvoiceCommand。这是发生了什么:
命令调度器/处理器接收的命令的同时。
对于每个命令,它从存储库中加载汇总,并存储汇总版本,假设是10。
对于每个命令,处理程序都会在集合体上调用添加发票的方法,然后接收ItemWasAddedToInvoiceEvent两次,每次执行命令处理程序时都会收到一次。
对于每个命令,处理程序都会尝试将事件持久化到 Event store
对于第一个事件持久动作(始终是纳秒级的第一个事件),该事件将持久并且聚合版本增加;现在是11;
对于第二个命令处理程序生成的第二个相同事件,该事件不会持续存在,因为10找不到所需的版本,如现在所示11。因此,存储库重试命令执行(了解这一点非常重要)。也就是说,再次调用所有命令执行:从版本现在为版本的存储库中再次加载聚合11,然后在其上应用命令,然后将事件持久保存到事件存储中。
因此,将两个事件添加到事件存储中,因此发票具有正确的状态:现在有4个项目,其正确的总价为所有4个项目的总和。