事件采购模式中的聚合

时间:2018-04-23 15:50:51

标签: java aggregate microservices event-sourcing

我正在尝试采用事件采购模式并尝试理解聚合。我已经阅读了一些博客,现在我比以前更加困惑。

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

用例:

  1. 我想在发票上重播事件,我希望看到特定员工在余额上完成的所有操作。

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

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

    活动商店:

    | 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包含有关付款变更,调整和发票状态的信息 状态是(审核,结算,付费)

    因此,根据我的理解,需要有5个组件。

    1. 活动 - 特定活动。
    2. 事件源 - 调用repo获取相关事件的服务
    3. 事件流 - 事件列表
    4. 命令 - 发票上的请求操作
    5. 聚合 - 决定加载事件输入的API
    6. 我理解其他事情是如何发挥的,但我很难绕过Aggregate。它是什么?

      我是否有两个聚合类

      • AggregateEventsByInvoice
      • AggregateEventsByInvoiceEmployee

      我真的很难弄清楚聚合的需要和使用。我见过的所有例子都使用了对我来说根本没有意义的UUID?任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:4)

  

我正在尝试采用事件采购模式并尝试理解聚合。我已经阅读了一些博客,现在我比以前更加困惑。

这不是你的错。

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

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

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

如果我们仔细检查域名,我们经常会在模型中找到具有有趣属性的数据集群:更改集群状态的规则不依赖于集群外的任何信息。 / p>

示例:在交易应用程序中,匹配和处理某些商品的出价和报价。但是匹配一种商品(黄金)的规则完全独立于与不同商品(冷冻浓缩橙汁)相关的数据。您不需要知道FCOJ中正在处理黄金交易活动的情况,反之亦然。

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

该隔离的两个关键属性是

  • 对聚合内部状态的更改不依赖于聚合之外的更改
  • 在汇总之外进行的更改不依赖于汇总内的更改

所以在这个例子中,我们可能会有一个TradeBook"聚合"对于Gold,以及TradeBook"聚合"对于FCOJ。要处理订单,您需要加载所需的汇总,对其应用更改并保存,而无需触及另一个。

  

我是否有两个聚合类

     
      
  • AggregateEventsByInvoice
  •   
  • AggregateEventsByInvoiceEmployee
  •   

不,您可能会根据相同的事件历史记录有两个视图投影

更确切地说,在埃文斯描述的架构中,会有一个 "聚合root",并且每个用例在API中都是该聚合的不同查询。

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

  

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

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

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

  

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

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

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

  

事件存储是否可以拥有非聚合ID的其他列

当然 - 您可以在活动中存储您喜欢的任何元数据。创建其他列可以提高查询性能,更轻松地对数据进行分片等等。

  

我们是否可以尝试从事件存储中加载查询列的事件以及聚合它? (在这种情况下,发票ID,员工ID)。

当然,您可以按照自己喜欢的方式查询事件。

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

在您的示例中,事件[1,2,3,4,5]合在一起讲述了有关发票的连贯故事。但是,尝试从事件[4]单独创建对发票的理解可能无法让您随时随地。

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

答案 1 :(得分:0)

在事件采购中,聚合是一个对象,其状态(字段)未映射到数据库中的记录,就像我们在SQL / JPA世界中通常认为的那样。

不是一组相关实体。

这是一组相关记录,例如历史记录表中的

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

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

现在,我们可以问自己这是怎么做的?谁在汇总这些事件,因此我们仍在使用一个字段(例如GiftCard.amount)进行操作?我们可能希望该金额是一个Collection而不是大十进制类型。

由事件源引擎来完成工作,他们可以简单地按创建顺序重播所有事件。

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

我们如何找到所有以第一笔赎回金额为全部金额的礼品卡?如何回答各种问题?我们可以想象(SQL)查询非常复杂且缓慢。

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

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

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

用于汇总事件的命令和用于读取数据的查询。而且命令和查询的职责是分开的。