事件Sourcing/CQRS 关于聚合、原子性、并发和最终一致性的疑惑
Event Sourcing/CQRS doubts about aggregates, atomicity, concurrency and eventual consistency
我正在研究事件溯源和command/query隔离,我有一些疑问希望有更多经验的人能够轻松回答:
- A) 一个命令处理程序是否应该处理多个聚合? (a.k.a。他们应该协调几个聚合之间的事情吗?)
- B) 如果我的命令处理程序生成多个要存储的事件,你们如何将所有这些事件自动推送到事件存储? (我如何保证没有其他命令处理程序会在两者之间“交错”事件?)
- C) 在我读到的许多文章中,人们建议使用乐观锁定来编写生成的新事件,但在我的用例中,我将有大约 100 个请求/秒。这让我觉得 很多 的请求会以很高的速率失败(很多 ConcurrencyExceptions),你们是如何处理这个的?
- D) 如何处理命令处理程序在将事件存储在事件存储之后但在将它们发布到事件总线之前崩溃的事实? (如何最终将那些“已确认”的事件推送回事件总线?)
- E) 你们如何处理预测的最终一致性?你只是忍受它吗?或者在某些情况下人们也会把东西锁在那里? (例如等待更新)
我制作了一个序列图来更好地说明所有这些问题
(抱歉英语不好)
If my command handler generates more than one event to store, how do you guys push all those events atomically to the event store?
最合理的事件存储实现将允许您将多个事件批处理到同一个事务中。
In many articles I read people suggest using optimistic locking to write the new events generated, but in my use case I will have around 100 requests / second.
如果您有很多并行线程试图维护一个复杂的不变量,那么就出现了严重错误。
对于不希望建立或保持任何不变量的“事件”,您只是将内容写入流的末尾。换句话说,您可能不会尝试将事件写入流中的特定位置。所以你可以使用批处理来减少冲突写入的数量,以及一个简单的重试机制。实际上,您使用的是与并发写入者插入队列时出现的相同类型的“扇入”模式。
对于 establishing/maintaining 不变量的情况,通常不会有很多并发编写器。相反,特定的作者有权编写事件(想想“分片”);那里的并发控制主要是为了避免在异常情况下造成混乱。
How to deal with the fact that the command handler can crash after storing the events in the event store but before publishing them to the event bus?
使用拉取而不是推送作为主要的订阅机制。确保订阅者可以安全地处理重复的消息(又名“幂等”)。当您需要严格排序事件时,不要使用可以重新排序事件的消息订阅。
How you guys deal with the eventual consistency in the projections? you just live with it?
差不多。视图和报告中包含元数据信息,让您知道报告在“时间”的哪个固定点是准确的。
除非您在使用报告时锁定所有编写者,否则任何数据都有可能过时,无论您使用的是事件模型还是其他数据模型,无论您是否使用单一数据模型数据模型或多个。
这都是权衡的一部分;我们接受报告时间和当前时间之间会有更大的window,以换取较低的响应延迟、“不可变”事件历史等
should a command handler work with more than one aggregate?
可能不会 - 这与永远不会是一回事。
通常的框架是这样的:聚合不是域建模模式,就像实体一样。这是一种生命周期模式,用于确保我们一次所做的所有更改都是一致的。
如果您发现您希望命令处理程序同时修改多个域实体,并且这些实体属于不同的聚合,那么您是否真的选择了正确的聚合边界?
有时您可以做的是使用一个命令处理程序来管理多个事务,并在每个事务中更新不同的聚合。但在较长的 运行 中,拥有两个不同的命令处理程序可能更容易,每个处理程序接收命令的副本并独立决定要做什么。
我正在研究事件溯源和command/query隔离,我有一些疑问希望有更多经验的人能够轻松回答:
- A) 一个命令处理程序是否应该处理多个聚合? (a.k.a。他们应该协调几个聚合之间的事情吗?)
- B) 如果我的命令处理程序生成多个要存储的事件,你们如何将所有这些事件自动推送到事件存储? (我如何保证没有其他命令处理程序会在两者之间“交错”事件?)
- C) 在我读到的许多文章中,人们建议使用乐观锁定来编写生成的新事件,但在我的用例中,我将有大约 100 个请求/秒。这让我觉得 很多 的请求会以很高的速率失败(很多 ConcurrencyExceptions),你们是如何处理这个的?
- D) 如何处理命令处理程序在将事件存储在事件存储之后但在将它们发布到事件总线之前崩溃的事实? (如何最终将那些“已确认”的事件推送回事件总线?)
- E) 你们如何处理预测的最终一致性?你只是忍受它吗?或者在某些情况下人们也会把东西锁在那里? (例如等待更新)
我制作了一个序列图来更好地说明所有这些问题
(抱歉英语不好)
If my command handler generates more than one event to store, how do you guys push all those events atomically to the event store?
最合理的事件存储实现将允许您将多个事件批处理到同一个事务中。
In many articles I read people suggest using optimistic locking to write the new events generated, but in my use case I will have around 100 requests / second.
如果您有很多并行线程试图维护一个复杂的不变量,那么就出现了严重错误。
对于不希望建立或保持任何不变量的“事件”,您只是将内容写入流的末尾。换句话说,您可能不会尝试将事件写入流中的特定位置。所以你可以使用批处理来减少冲突写入的数量,以及一个简单的重试机制。实际上,您使用的是与并发写入者插入队列时出现的相同类型的“扇入”模式。
对于 establishing/maintaining 不变量的情况,通常不会有很多并发编写器。相反,特定的作者有权编写事件(想想“分片”);那里的并发控制主要是为了避免在异常情况下造成混乱。
How to deal with the fact that the command handler can crash after storing the events in the event store but before publishing them to the event bus?
使用拉取而不是推送作为主要的订阅机制。确保订阅者可以安全地处理重复的消息(又名“幂等”)。当您需要严格排序事件时,不要使用可以重新排序事件的消息订阅。
How you guys deal with the eventual consistency in the projections? you just live with it?
差不多。视图和报告中包含元数据信息,让您知道报告在“时间”的哪个固定点是准确的。
除非您在使用报告时锁定所有编写者,否则任何数据都有可能过时,无论您使用的是事件模型还是其他数据模型,无论您是否使用单一数据模型数据模型或多个。
这都是权衡的一部分;我们接受报告时间和当前时间之间会有更大的window,以换取较低的响应延迟、“不可变”事件历史等
should a command handler work with more than one aggregate?
可能不会 - 这与永远不会是一回事。
通常的框架是这样的:聚合不是域建模模式,就像实体一样。这是一种生命周期模式,用于确保我们一次所做的所有更改都是一致的。
如果您发现您希望命令处理程序同时修改多个域实体,并且这些实体属于不同的聚合,那么您是否真的选择了正确的聚合边界?
有时您可以做的是使用一个命令处理程序来管理多个事务,并在每个事务中更新不同的聚合。但在较长的 运行 中,拥有两个不同的命令处理程序可能更容易,每个处理程序接收命令的副本并独立决定要做什么。