我可以看到如何命令可以在与CQRS + ES模式的时间序列的严格一个来执行。具有并行执行多个命令会显著提高系统的吞吐量,并利用我所有的服务器上的内核。但可以肯定这种风险并发问题?
举个简单的例子,两个用户更新客户名称,并在同一时间提交的命令。在两个平行的过程中重播以前的事件得到聚集到最新状态,然后双方决定一切是有效的。无论然后保存一个新的更新事件。无论哪一个省最后似乎是赢家,因为任何未来的重播将导致在最后一次更新是客户的名称。
如果命令的结果是什么,以发送电子邮件给客户。现在我们发送两封电子邮件,而不是预期的一个。如果在使用客户特性创建一个被连接到客户一个新的聚集命令的结果,我们最终创建了两个新的聚集,但一个什么是僵尸,因为,因为其他聚合已被保存,因为它永远不会被客户引用而不是参考。
这感觉就像你需要实现自己的事务锁定功能,保证你永远不会得到改写,这是一个复杂的解决方案,我宁愿避免!
更新:
我的例子有点太简单了。试想一下,我们有一个进入的发票发票和项目条目。从发票添加/更改/删除项目后,我们需要找到新的总并将其设置到Invoice.Total属性字段。
如果在两个并联的用户都添加一个新的项目,则总将是错误的,没有明确的锁定机制。双方重新创建发票总量和项目儿童的现有列表汇总该发票。在两个平行创建一个新的项目,然后添加了新的总并将其设置为Invoice.Total财产。
无论然后保存这些动作的事件。但是现在我们有了,因为无论哪个命令完最后一共会有不包括来自洗完头实例的新项目出现错误。这有没有关系悲观/乐观并发但交易锁定。
我可以看到如何命令可以在与CQRS + ES模式的时间序列的严格一个来执行。具有并行执行多个命令会显著提高系统的吞吐量,并利用我所有的服务器上的内核。但可以肯定这种风险并发问题?
从视CQRS+ES
点没有并发问题。任何一个优秀的Event store
实现了守卫与并发事件除了在同一Aggregate
,例如使用乐观锁定一个Aggregate
版本约束。
UPDATE:CQRS+ES
喜爱和欢迎并发命令。
如果命令的结果是什么,以发送电子邮件给客户。现在我们发送两封电子邮件,而不是预期的一个。
由谁预期?再次,这是不是一个CQRS+ES
问题。这是企业的设计问题。在这种特殊情况下,你应该已经设计了另一界上下文Aggregate
与发送电子邮件给客户,有业务不变的是确保对于一些主题(即用户名变更)多封电子邮件被分成一个一个的交易,具有较高优先级的最后一个。
如果在使用客户特性创建一个被连接到客户一个新的聚集命令的结果,我们最终创建了两个新的聚集,但一个什么是僵尸,因为,因为其他聚合已被保存,因为它永远不会被客户引用而不是参考。
再次,它取决于你的业务。从视CQRS + ES点是没有问题的。事件商店在处理大量数据的是巨大的(这是因为事件存储是一个只添加持久性)。这僵尸骨料是无害的。它只占了事件存储的一些事件。在突出部(读取模式)没有疼痛,因为它不存在。
这感觉就像你需要实现自己的事务锁定功能,保证你永远不会得到改写,这是一个复杂的解决方案,我宁愿避免!
这是,你将不得不使用无关CQRS + ES的问题。如果这是一个业务需求,你无论如何都要做到这一点。
在两个用户在同一时间,你可以做一些额外的检查,并告知失去用户,其他用户修改了用户名,更改用户名的情况下,甚至可能呈现什么样的用户名,让他来决定下一步做什么:重试或取消。
AFTER UPDATE:
我的例子有点太简单了。试想一下,我们有一个进入的发票发票和项目条目。从发票添加/更改/删除项目后,我们需要找到新的总并将其设置到Invoice.Total属性字段。
如果在两个并联的用户都添加一个新的项目,则总将是错误的,没有明确的锁定机制。双方重新创建发票总量和项目儿童的现有列表汇总该发票。在两个平行创建一个新的项目,然后添加了新的总并将其设置为Invoice.Total财产。
无论然后保存这些动作的事件。但是现在我们有了,因为无论哪个命令完最后一共会有不包括来自洗完头实例的新项目出现错误。这有没有关系悲观/乐观并发但交易锁定
我认为你必须了解Event sourcing
是如何工作的:让我们假设两个用户在他们的Invoice
前(其实这是他们看到的,不是Cart
的Invoice
;一个Invoice
放置后产生的Order
,但对于简单起见,我们假设)。让我们假设他们看到2项和他们每个人都希望增加新项目。他们两人发起的命令:AddAnItemToInvoiceCommand
。这是发生了什么:
10
。ItemWasAddedToInvoiceEvent
两次,一个用于每个命令处理程序的执行的骨料的方法。Event store
11
;10
没有发现,因为现在是11
。因此,库重试commnand执行(这是非常重要的理解)。也就是说,所有命令的执行又被称为:总再次从仓库处的版本11
装,现在,则该命令适用于它,然后该事件被保存到事件存储。因此,两个事件被添加到事件存储,所以发票有正确的状态:现在有4项与正确的总价格是所有4总和。
这是不是真的在全部cqrs问题;它是真正具体到事件存储,以及事件的特定存储策略。
举个简单的例子,两个用户更新客户名称,并在同一时间提交的命令。在两个平行的过程中重播以前的事件得到聚集到最新状态,然后双方决定一切是有效的。无论然后保存一个新的更新事件。无论哪一个省最后似乎是赢家,因为任何未来的重播将导致在最后一次更新是客户的名称。
有几件事情需要注意 - 一个是,如果这些命令顺序运行同样的问题存在;第二撤消第一的影响。这正是你想在某些情况下的行为;例如固定的拼写错误。
其次,注意,因为你只是追加,编辑都被保留;两个变化都在流中存在的;它的视图(倍),而不是流中,其确定其中两个的编辑获胜。
这意味着,在一定程度上,你都没有做,但 - 你仍然需要在战略选择“胜利者”正确模拟。
在编辑可能会发生冲突的情况下,那么你就需要采取防范允许未承认所有以前接受的写入的写入。在这种情况下,你不想追加到流,而是比较和交换。
插话
使用传统的数据库写入的动作,并从自动记录阅读为你处理锁定,它是数据库一样。在CQRS + ES否则它是一个数据库之外,所以我需要做手工。
你的困惑的一部分,我相信,是你用混为一谈更新历史追加事件。文学是不是清楚这一点,所以它可能是有价值的,审查的替代方法,以保持历史。
试想一下,片刻,你保留你的状态不是事件流,但作为事件的文件。在幸福路,您加载文件,修改你的本地副本,你的版本替换以前的副本。
在并发写的情况下,我们有两位作家是加载文档,做出不同的修改,然后每尝试做替换。怎么了?如果我们什么也不做,以减轻并发写入,我们可能与最后的写入最终获胜的策略 - 通过删除由以前的作家所做的编辑破坏我们的历史。
为了确保我们不会失去写入,我们需要从文档存储的帮助。具体来说,我们希望有条件的put(例如,mongodb:findAndMofidy)的一些模拟。第二个作家应该得到ConcurrentModification响应,它可以减轻随后(失败,重试,合并)的一番风味。
当我们在做活动的采购,因为她谈到了这个没有任何变化。
什么变化是我们“PUT”事物的本质;因为我们正在接受纪律的事件的陈述是不可变的,而事件的收集只是追加,我们已经限制文档的有效编辑了一组,使我们能够优化掉每次传送整个文件。
如果你喜欢,你可以想像,我们发送到事件存储的,我们要对文档更改的说明。所以店里加载文件的副本,适用我们的更改,然后存储更新的版本。
但域模型没有验证的更改应用到历史文件的任意版本,但它的一个特定版本。所以我们发送来形容我们的更改消息需要包括到我们从开始的文档版本的引用。
你可以想像的是,“事件存储”是在内存中的链接列表。 “追加”是类似的更新文档,而无需确保它是你验证什么不变。如果我们需要确保我们的假设仍然有效(即乐观并发),那么我们就需要比较并交换尾指针;如果比赛已经允许,因为我们看了一些其他作家更新的尾巴,那么CAS操作失败,使我们能够恢复。
远程事件存储需要提供类似的接口:看到使用expectedVersion in GetEventStore的。
您可能会发现有价值的审查collaborative domains乌迪大汉的著作。
这感觉就像你需要实现自己的事务锁定功能,保证你永远不会得到改写,这是一个复杂的解决方案,我宁愿避免!
你应该记住,你对某种当你决定使用并行作家锁定的签约!做的工作,以控制如何保持数据的完整性是权衡你签上的一部分。
好消息是,你并不需要实现一个交易锁定策略;你需要选择一个数据存储,提供了一个交易锁定策略。
比较和交换真的没有那么复杂,它的just complicated。
无论然后保存这些动作的事件。但是现在我们有了,因为无论哪个命令完最后一共会有不包括来自洗完头实例的新项目出现错误。这有没有关系悲观/乐观并发但交易锁定。
正确的 - 你不能一味追加到你期望的不变量来保存流。你必须确保你附加到已选中的位置。
还有一种替代的设计,您可能会看到不时,即把历史作为一个图形,而不是作为一个流。因此,每个写可能叉的历史,并在必要时稍后要合并。你可以得到一个感觉如何可能的工作回顾Kleppmann's work on CRDTs
我的阅读告诉我,事件图仍牢牢掌握在复杂的象限。
如果两个命令都对总的不同实例执行 - 答案是微不足道的,当然也可以。每个聚集是它自己的事务边界。
如果两个命令都在同一个实例上执行的,你有三种选择来处理并发:
另一种解决方案可能是它运行的事件的实体是单线程的,所以如果你有多个航班,然后你可以为每个航班这样一个单独的线程。
飞行A - 一个线程处理事件的话,一个单一的事件将更新那么这两个聚集这么一个更新将不会重叠另一个更新。