【问题标题】:Aggregates in Event Sourcing Pattern事件溯源模式中的聚合
【发布时间】:2018-10-03 17:45:03
【问题描述】:

我正在涉足事件溯源模式并试图理解聚合。我已经阅读了一些博客,现在我比以往任何时候都更加困惑。

据我推断,聚合应该以某种方式使用户能够在事件存储上运行不同的查询来检索不同的事件流。

用例:

  1. 我想重播发票上的事件,我想查看特定员工在余额中执行的所有操作。

  2. 我想重播发票上的所有事件

我希望这些是有效的用例。

活动商店:

| event_id | invoice_id | EmployeeId | Event            | Payload |
|----------|------------|------------|------------------|---------|
| 1        | 12345      | 12345      | Invoice_InReview | JSON    |
| 2        | 12345      | 12345      | Invoice_Billed   | JSON    |
| 3        | 12345      | 45567      | Invoice_Paid     | JSON    |
| 4        | 12345      | 77341      | Invoice_Reversed | JSON    |
| 5        | 12345      | 98421      | Invoice_Paid     | JSON    |

JSON 包含有关付款更改、调整和发票状态的信息 状态为(Review,Billed,Paid)

所以据我了解,需要 5 个组件。

  1. 事件 - 特定事件。
  2. 事件源 - 调用 repo 获取相关事件的服务
  3. 事件流 - 事件列表
  4. 命令 - 发票请求操作
  5. 聚合 - 决定加载事件输入的 api

我了解其他事物是如何发挥作用的,但我很难理解 Aggregate。这是什么?

我会有两个聚合类

  • AggregateEventsByInvoice
  • AggregateEventsByInvoiceEmployee

我真的很难弄清楚 aggregate 的需求和用途。我见过的所有示例都使用 UUID,这对我来说根本没有意义?任何帮助将不胜感激。

【问题讨论】:

标签: java aggregate microservices event-sourcing


【解决方案1】:

在事件溯源中,聚合是一个对象,其状态(字段)未映射到数据库中的记录,正如我们在 SQL/JPA 世界中所认为的那样。

不是一组相关的实体。

它是一组相关的记录,就像在历史表中。

GiftCard.amount 是 GiftCard Aggregate 中的一个字段,但此字段映射到所有事件,例如曾经创建的卡兑现(从卡中取钱)。

您的聚合的数据源不是数据库中的记录,而是曾经为该特定聚合创建的事件的完整列表。我们说我们事件来源聚合。

现在我们可以问自己它是如何完成的?谁在汇总这些事件,所以我们仍然使用一个字段(例如 GiftCard.amount)进行操作?我们可能期望该数量是一个集合,而不是一个大十进制类型。

是事件溯源引擎,在做这项工作,它可能只是按创建顺序重放所有事件。

将事件(聚合的状态)存储在数据库/事件存储/等中,然后我们问自己如何从这些事件中获得任何有意义或具体的见解?

我们如何找到第一次兑换金额为全部金额的所有礼品卡?如何回答各种问题?我们可以想象(SQL)查询是相当复杂和缓慢的。

事件存储没有针对查询(读取数据)进行优化,而是针对写入数据进行了优化。

因此,为了读取/查询数据,您将拥有第二个数据库/模式/弹性搜索等,针对快速读取数据进行了优化。

聚合具有完整状态,接受命令,在发生更改时发出事件,并且这些事件正在更新读取模型(第二个数据库/模式/弹性搜索等)

用于聚合事件的命令和用于读取数据的查询。并且命令和查询职责是分开的。

【讨论】:

    【解决方案2】:

    我正在涉足事件溯源模式并试图理解聚合。我已经阅读了一些博客,现在我比以往任何时候都更加困惑。

    这不是你的错。

    聚合的概念来自 Eric Evans 对领域建模的描述。

    在典型的部署中,我们有一个数据库,其中包含我们想要跟踪的事实。我们有一个模型,其中这些事实会随着时间而改变。我们希望确保正确跟踪这些更改,这意味着不会引入不一致。

    对此的粗略回答是,我们将数据库“置于”一个域模型的“后面”,该域模型包含了关于如何允许数据库中的数据更改的所有规则。在 Evans 时代,领域模型是位于应用层和持久层之间的一层。这些天来,您更可能听到“组件”或“模块”而不是层,但角色并没有太大变化:保护数据库免受不正确的更改。

    如果我们仔细检查域,我们会经常在模型集群中发现表现出有趣属性的数据:更改集群状态的规则不依赖于集群外部的任何信息。

    示例:在交易应用程序中,匹配和处理某些商品的出价和出价。但是匹配一种商品(黄金)的规则完全独立于与另一种商品(冷冻浓缩橙汁)相关的数据。您无需了解 FCOJ 中发生的任何事情即可正确处理黄金交易中的活动,反之亦然。

    这些可以单独考虑的集群是聚合

    这种隔离的两个关键属性是

    • 聚合内部状态的更改不依赖于聚合外部所做的更改
    • 在聚合外部进行的更改不依赖于在聚合内部进行的更改

    因此,在此示例中,我们可能有一个用于黄金的 TradeBook“聚合”,以及一个用于 FCOJ 的 TradeBook“聚合”。要处理订单,您需要加载所需的聚合、对其应用更改并保存它,而无需接触另一个。

    我会有两个聚合类

    • AggregateEventsByInvoice
    • AggregateEventsByInvoiceEmployee

    不,您可能会有两个 viewsprojections 基于相同的事件历史记录。

    更准确地说,在埃文斯描述的架构中,会有一个 “聚合根”,并且您的每个用例都将是 API 中针对该聚合的不同查询。

    但最近,实践是认识到读取的用例不需要与写入用例相同的约束。因此,今天您更有可能看到每个用例的 view(或 projection),其中每个用例的内存表示是根据记录在您的数据存储。

    所以我的理解是聚合本质上是可以唯一识别与单个实例相关的所有事件的任何东西(在我的情况下是发票)。那么在我的情况下,invoiceId 可以被视为一个聚合吗?

    没有。在您的情况下,发票很可能是合计。

    更准确地说,您的域模型可能正在协调每张发票的余额、调整、状态和付款之间的变化;这些值是我之前谈到的那种集群的一个例子。您可以对这些值进行更改而无需考虑,例如调整 Invoice[67890]。

    所以我的理解是聚合本质上是可以唯一识别与单个实例相关的所有事件的任何东西(在我的情况下是发票)。

    一个问题是,这种理解与现有文献不一致,很可能导致沟通混乱。

    在文档存储或键值存储中,聚合类似于文档,而不是用于查找文档的键。在 RDBMS 中,聚合将是相关实体,id 将是您用来加载实体的主键。在事件存储中,流的内容描述聚合中事件的变化,id 只是您用来查找正确事件的键。

    事件存储是否可以拥有非聚合 id 的附加列

    当然 - 您可以在活动中存储您喜欢的任何元数据。创建额外的列可以提高您的查询性能,使您的数据更容易分片,等等。

    除了聚合之外,我们还可以尝试从查询列的事件存储中加载事件吗? (invoice id , employee id) 在这种情况下。

    当然,您可以随意查询事件。

    通过重放任意一组事件来尝试恢复域模型的当前状态可能不是一个好主意。

    在您的示例中,事件[1,2,3,4,5] 一起讲述了一个关于发票的连贯故事。但尝试从事件 [4] 本身创建对发票的理解可能不会让您有任何收获。

    请记住,事件通常不是模型更改后状态的完整表示,而是对已更改事物的描述。想想“补丁”,而不是“快照”。

    【讨论】:

    • 所以我的理解是聚合本质上是可以唯一标识与单个实例相关的所有事件的任何东西(在我的情况下是发票)。那么在我的情况下,invoiceId 可以被视为一个聚合吗?
    • 问题是否根据用例汇总 invoiceId。 2 个问题 1) 事件存储是否可以拥有非聚合 id 的附加列? (此处的员工 ID) 2)我们是否可以尝试从事件存储中加载事件来查询列上的事件以及聚合它? (invoice id , employee id) 在这种情况下。
    • 可以添加额外的元数据列 - 通常会添加审计信息(谁进行了更改、时间戳等),并且通常会添加关联 ID 和/或请求 ID(将事件链接到导致它的命令或进程管理器)。在直接从事件构建投影或提供读取查询时,您可以只查询事件的子集(有时您只关心特定的事件类型)。显然,这在处理命令时没有用,因为您需要将整个流用于聚合。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 2019-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多