【问题标题】:Event sourcing with inconsistent behavior of events事件行为不一致的事件溯源
【发布时间】:2017-10-04 15:15:56
【问题描述】:

我正在尝试使用 Postgres 实现事件溯源系统。 最好的解释方式是举例。 所以让我们假设我的事件最终需要描述一个人。 我有以下事件:

  1. PersonCreatedEvent(value = 100, field = "id", date = ..)
  2. AgeUpdated(值 = 10,字段 = “年龄”,日期 = ..)
  3. LastNameUpdate(value = "newLastName", field = "lastname", date = ..)
  4. Lo​​cationUpdated(value = (lat, long), field = "location", date = ..)
  5. BalanceUpdated(value = 1000, field = "balance", date = ..)

事件 1 在人的一生中只会发生一次。
事件 2 - 3 可能发生多次。
事件 4 - 5 可以发生很多次。

最终,我将得到一个主要与事件 4 和 5 一致的表格。

因此,如果我的表中有 1000 万人,我可能有数十亿个事件,而其中 99% 基本上是 4 和 5。这将导致一个巨大的数据存储,不确定 Postgres 是否能很好地处理它.(它可以..但随着大量工作\基础设施的增加。)

这只是一个示例,我的实体可能由 100 个字段组合而成,这意味着每个实体至少有 100 个事件。部分字段具有事件4和事件5的特征。

在我的案例中使用事件溯源的附加价值是我获得了关于我的实体的固有历史记录,这在我的案例中是产品要求。

在这种情况下,最佳做法是什么? 也许更频繁的属性应该在其他地方处理?

更新: 另一个例子是查看设备聚合。

  1. DeviceManufacturerUpdated(value = "cisco", field = "manufacturer", date = ..)
  2. DeviceNameUpdated(value = "foo", field = "name", date = ...)
  3. DeviceIpUpdated(value = "1.1.1.1", field = "ip", date = ...)
  4. DeviceLocationUpdated(value = "新位置", field = "location", date = ...)
  5. DeviceLastSeenUpdated(value = "some date", field = "last_seen), date = ...)

这里也一样 1. 事件 1 可以发生一次 2. 事件 2 可以发生多次 3.事件3可能每天都在发生 4. 事件 4 可能一天发生几次 5. 事件 5 可能每分钟发生一次

如果我在 postgress 上实现它,我最终会得到一个巨大的表,其中主要包含事件 4 和 5。

【问题讨论】:

  • 快照可能会有所帮助,但我认为在某些时候我需要对我的数据库大小做一些事情。 (大小是我坚持的事件“类型”的结果)。所以在某些时候会留下快照,但我会失去历史记录功能(删除事件后)。当我的表包含 99% 相同的事件类型时,感觉就像我做错了什么。感觉好像还有我不知道的优化空间..:-|你怎么看?

标签: cqrs event-sourcing


【解决方案1】:

对我来说,你似乎有一个(粒度)CRUD 系统,你不需要Event sourcing。这还不错,您只是不需要通过重新应用历史记录中的所有事件来重建状态来验证命令。在您的情况下,也许当前状态就足够了。

如果您喜欢事件的概念(更像是如果您需要),那么您可以使用Event log。这类似于事件溯源,但您以最终一致的方式使用事件,并且仅用于读取端。

无论如何,为了按行为拆分实体,您可以使用DDD。也许您可以有两个共享相同 ID 的聚合:PersonDetailsPersonAccountBalance?为什么?因为当您更新他的余额时检查他的余额是否应该是正数时,您永远不需要该人的姓名(或位置)(关于这一点,您 update his balance 而不是 makeADepositwithdrawMoney 但它很奇怪取决于您的域)。

【讨论】:

  • 谢谢!我认为 BalanceUpdated 有点误导......这是一个不好的例子。当我想获取某人的历史并在任何时间描述他时,使用事件溯源是有意义的。让我们解决聚合的人。如果它有一个非常频繁更新的属性,是否应该以不同的方式解决它?我的意思是,当我的表包含 99% 的相同事件类型时,感觉有些不对劲,
  • @Tomer 这真的取决于每种情况。我需要知道确切的业务细节才能确定地说...
  • 你完全正确。我添加了一个更好地代表我的用例的示例。告诉我你的想法。
  • @Tomer 我建议您仅在聚合内部发生真正变化的情况下才发出事件,否则不会发出任何内容,只需返回 void,这是一个完美的有效情况。例如,除非位置真的改变了,否则不要发出DeviceLocationUpdated,它有一个新的不同值。但请记住我对您的 CRUD-ish 域的回答。
  • 当然,只有当当前状态与我得到的不同时才会生成事件。这可能会发生很多次。我认为主要问题是在这种情况下使用事件溯源来获取设备的历史记录是否更好。还是应该以不同的方式实现,例如 2 个表,1 个当前状态,以及 otehr 一个历史表
【解决方案2】:

我的实体可能包含 100 个字段,这意味着每个实体至少有 100 个事件

可能表明您的事件以错误的方式模拟您的实体。影响数百个属性的域中发生的一件“事情”通常应该作为一个包含大量数据的事件出现在您的历史记录中,而不是每个事件都有一个数据。

这并不是说您最终不会遇到大量事件,尤其是在您有很多实体的情况下。

对此有两个常见的答案。一借;我们从聚合的角度来考虑域 - 可以相互隔离的状态集合。因为实体可以被隔离,所以它们是否在同一个持久存储中,或者存储是否分片都没有关系。这样你就可以分发事件了。

另一种可能性——在许多领域,存在自然的时间节奏;在财政年度结束时,您汇总当前状态,使用该状态标记新流的开始,然后淘汰旧流。

还有垃圾收集的可能性——有了正确的数据结构,您可以让后台进程遍历历史,替换低价值事件。

主要是要摆脱粗略采购的概念。

更广泛地说,您还需要考虑企业是否通过跟踪实体的整个历史来获得价值。对于成本/收益分析告诉您将信息存储在文档快照中的实体,您不应使用事件历史记录。

【讨论】:

    【解决方案3】:

    对我来说,在同一个聚合根目录中拥有 AgeUpdated 和 BalanceUpdated 事件看起来很奇怪。名称中有SomethingUpdated 的事件告诉我,它要么是 CRUD 用例,要么您需要在聚合设计上做更多工作。

    是否有另一组交易可以代替 Person 更有趣?要弄清楚这一点,您应该考虑应该保留哪些不变量。也就是说,在哪些情况下事务会失败,什么情况应该总是正确的? (如果您还没有,请阅读领域驱动设计聚合)

    例如,如果您正在为帐户进行某种交易,那么业务规则可能是该帐户不能透支超过 100 美元。在这种情况下,您可以使用 Account 作为聚合根而不是拥有该帐户的 Person。

    要获得一个人的全部平衡,您需要一个 read-model 来为用户聚合所有内容。

    但一切都取决于您的用例。

    祝你好运!

    【讨论】:

    • 谢谢!我认为 BalanceUpdated 有点误导。因为它确实不属于 person 聚合。让我们假设该事件与 Person 聚合相关(也许 locationupdated 更好?),我应该如何处理这个事件确实在正确的位置,但它的频率比其他事件大 *100 的情况?
    • 这取决于您关心的问题。我认为聚合根有许多类似的事件本质上并不是错误的。如果您担心性能,请确保在实现更复杂的东西(例如快照)之前衡量它确实是一个问题。如果您想在不构建太多基础架构的情况下试验您的域逻辑,那么如果您想尝试我们的新服务 serialized.io 来构建 CQRS/ES 应用程序(我是联合创始人之一),那将是很酷的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-16
    • 2017-04-06
    • 2020-05-27
    • 2021-06-24
    • 2022-10-23
    相关资源
    最近更新 更多