【问题标题】:Why should the event store be on the write side?为什么事件存储应该在写端?
【发布时间】:2012-05-22 05:31:06
【问题描述】:

事件溯源被宣传为许多事情的奖励,例如。事件历史记录/审计跟踪、完整且一致的视图重新生成等。听起来很棒。我是一个粉丝。但这些都是读取端的实现细节,您可以通过将事件存储完全移动到读取端作为另一个订阅者来完成同样的操作。为什么不呢?

以下是一些想法:

  1. 视图/反规范化器本身并不关心事件存储。他们只处理来自域的事件。
  2. 将事件存储移动到读取端仍会为您提供事件历史记录/审计
  3. 您仍然可以从事件存储中重新生成视图。除了现在它不必是写入模型泄漏。给他阅读模范公民身份!

这似乎是一个将其保留在写入端的技术论据。这是来自 http://codebetter.com/gregyoung/2010/02/20/why-use-event-sourcing/ 的 Greg Young:

然而,使用存储当前状态快照的东西存在一些问题。最大的问题围绕着您向数据引入了两个模型这一事实。你有一个事件模型和一个代表当前状态的模型。

我觉得有趣的是术语“快照”,它最近也成为事件溯源中的一个显着术语。在写入端引入事件存储会增加加载聚合的一些开销。您可以争论多少开销,但这显然是一个感知或预期的问题,因为现在有从快照加载聚合和自快照以来的所有事件的概念。所以现在我们又有了……两个模型。不仅如此,我看到的快照建议旨在作为基础设施泄漏来实施,后台进程会遍历整个数据存储以保持性能。

在拍摄快照之后,快照之前的事件从写入的角度来看 100% 无用,除了……重建读取端!这似乎是错误的。

另一个与性能相关的话题:文件存储。有时我们需要将大型二进制文件附加到实体。从概念上讲,有时这些与实体相关联,但有时它们就是实体。将这些放在事件存储中意味着您必须在每次加载实体时物理加载该数据。这已经够糟糕的了,但想象一下其中有几个或数百个这样的大集合。我看到的每个答案基本上都是咬紧牙关并将uri传递给文件。这是一种逃避,会破坏分布式系统。

然后是维护。重建视图需要一个涉及事件存储的过程。因此,现在您编写的每个视图维护任务都进一步将您的写入模型绑定到使用事件存储......永远。

CQRS 的重点不是围绕读模型和写模型的用例根本不兼容吗?那么为什么我们要把读模型的东西放在写端,牺牲灵活性和性能,然后再把它们耦合起来。为什么要花时间?

总之,我很困惑。在我所坐的所有方面,事件存储作为读取模型细节更有意义。您仍然可以获得保留事件存储的许多好处,但您不会过度抽象写入端持久性,这可能会降低灵活性和性能。而且您不会通过泄漏的抽象和维护任务将您的读/写端耦合起来。

那么有人可以向我解释一个或多个令人信服的理由来保持它在写作方面吗?或者,为什么不应该将其作为维护/报告问题放在读取端?同样,我不是在质疑商店的用处。就在它应该去的地方:)

【问题讨论】:

  • 事件快照也不是一个“新概念”,这是我在 2006 年关于事件溯源的第一次演讲:)

标签: cqrs event-sourcing


【解决方案1】:

我认为这确实是一个很好的问题。将您的聚合视为一系列事件本身在写入方面很有用,使命令重试等更容易。但我同意创建事件似乎令人不安,然后如果您需要这种快照性能改进,则必须为持久性创建另一个对象模型。

您的聚合仅存储快照,但将事件发送到读取模型以投影到读取模型的系统我认为被称为“CQRS”,而不是“事件溯源”。如果您保留事件以进行重新投影,我想您将拥有一个兼具两者的系统。

但是你不会有三个定义吗?一种用于持久化聚合,一种用于传达状态更改,还有更多用于回答查询?

在这样的系统中,通过加载聚合并直接向他们提问来开始回答查询是很诱人的。虽然这不是以任何方式禁止的,但它确实倾向于开始导致这些聚合增加他们可能不需要的功能,更不用说复杂的线程和事务了。

【讨论】:

  • 谢谢。它没有增加定义的数量。请记住,使用事件溯源+快照,您实际上将 2 内置为 1。因此您有用于聚合的 EventStore+Snapshot、用于读取模型的事件和用于读取再生的 EventStore。另一种方式是用于聚合的快照、用于读取模型的事件和用于读取重新生成的 EventStore。也一样,只是EventStore+Snapshot拆分。
  • 您能否扩展“使命令重试等更容易”的评论?即,当写入端持续事件而不是不持续事件时,这更容易吗?
  • 另外,最后一点,假设您仍然无法按设计查询聚合。 IMO 事件存储的选择不会影响此设计决策。再次感谢您的回复。
  • 我想如果只是需要重试的命令,底层聚合的持久性是无关紧要的。编辑与创建它的命令相关的事件历史记录的能力很有帮助。但是您还是建议保留该事件存储。
【解决方案2】:

将事件存储放在写入端的一个原因可能是在事件成为“事实”并被分发/调度之前解决并发问题,例如通过对提交事件流的乐观锁定。这样,在写入方面,您可以确保解决对同一事件流(聚合)的并发“提交”,其中一个通过,另一个必须通过比较事件或传播以智能方式解决冲突与客户端发生冲突,从而拒绝该命令。

【讨论】:

  • 你能举个例子吗?并发可以并且总是在没有事件存储的情况下处理。但是我对您所说的“比较事件”的确切含义很感兴趣,因为这听起来像是如果不存储事件就无法处理。是否有某种智能解决方案可以让您通过事件更轻松地实施?
  • @JeremyRosenberg 我认为 romanb 在这里得到的是每个事件通常应该维护一个“版本”属性,该属性指示该聚合的当前版本。因此,在写入方面,在事件实际提交之前,您可以根据您 expectactual 聚合版本(即最后一个事件)的聚合版本进行乐观并发检查流中的版本)。这种方法解决了分布式系统中的问题,即您的聚合可能会变得陈旧,即事件已在其间提交。
【解决方案3】:

这是一个很久以前就有人指出我的问题。有很多原因可以更好地在写入端存储事件。

据我了解,您所谈论的架构是一种非常常见的架构,我认为……失败了。我们将域模型存储在关系数据库中,然后输出事件。您添加它们的扭曲,将读取端的事件保存在事件存储中。这可能会导致混乱。

您将遇到的第一个问题是发布您的活动。当我保存到数据库并发布到 MSMQ 时会发生什么(我死在中间)。因此在它们之间引入了 DTC。这是一件大事,应该像瘟疫一样避免分布式事务。它也非常低效,因为我可能使数据持久化两次(一次排队到数据库)。这将大大限制系统吞吐量(200-300 条消息/秒的 DTC 基准​​很常见,而事件只有 20-30k/秒是常见的)。

有些人通过在他们的数据库中放置一个包含事件并作为队列操作的表来解决 DTC 的需求。这将避免对 DTC 的需要,但这仍然会遇到下一个问题。

当您遇到错误时会发生什么?我知道您永远不会编写有错误的代码,但后来有一位 Jrs/维护开发人员使用该项目。例如,当域对象更改和引发的事件不匹配时会发生什么?假设您将域对象上的 State 设置为“LA”(硬编码),但您正确地将 Event 上的 State 设置为 cmd.State(“CT”)。

您将如何检测正在发生的此类错误?正在讨论的最大问题是现在有两个“真相”来源,即写入端的数据库和出现的事件流。没有办法证明它们是等价的。这将导致各种奇怪的错误。

【讨论】:

  • 拥有单一事实来源的论点是事件溯源 (IMO) 的主要观点,它迫使您在写入端保留事件存储。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-23
  • 2019-11-05
相关资源
最近更新 更多