在事件源中分配有发票的有界上下文

时间:2018-03-27 15:18:14

标签: elixir domain-driven-design event-sourcing actor-model commanded

在以战术域驱动设计为模型的事件源系统中,我无法处理以下情况:

  1. 选择项目在发票上。
  2. 所有选定的项目都构成发票,或者如果无法分配任何选定的项目,则不会分配任何项目且发票不存在。
  3. 不变量:商品不得包含多张发票。
  4. 只要有项目的发票存在,就应将这些项目转换为价格。
  5. 我的计划是

    • 发票汇总类型,因此发票的标识用于对商品进行分组,并存储计算出的价格。
    • 我会有一个Item聚合类型,它通过为每个聚合实例存储一个发票的单个引用来跟踪不变量。

    我想我需要针对新的Invoice聚合发出一个命令,其中包含要分配给新发票的现有项目ID列表。然后,这将发出有关发票创建的事件。

    然后会有人听取此事件并将其转换为命令列表,这些命令会将所选的每个项目分配给新发票。

    我发现这可能会失败:例如,在发出命令后,其中一个选定项目可能已分配给另一个发票。因此,我会以某种方式需要回滚所有未失败的作业,并宣布发票不再存在。

    另一方面,要计算发票上的价格,我需要知道最初选择的所有商品何时实际分配到发票,以确保发票留在这里。

    目前,我正在使用基于Erlang演员模型的Elixir中的Commanded CQRS / Event Sourcing框架。

    我的天真想法,源于与非分布式关系数据库一起工作的悠久历史,将整个情况放入分散在两个聚合上的同步事务中。但是框架似乎并不支持这一点,它或多或少地破坏了异步分布式聚合实现最终一致性的想法。

    因此,我正在为我的问题寻找合适的解决方案。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:1)

在您的情况下,您需要使用saga作为模式。

你的传奇的样本流程可以是:

FLOW _ 1

  1. 创建一个Invoice聚合,其中所有项目ID和状态已初始化并输出多个事件 - 一个InvoiceCreated,一个InvoiceCreatedForItem
  2. 某人(ProcessManager)侦听InvoiceCreatedForItem事件并尝试将项目分配给发票,然后使用命令MarkItemSUccessfullyAddedToInvoice更新发票。如果发生故障,则使用MarkItemAdditionFailureToInvoice更新Invoice。
  3. 在所有项目发生事件后,Invoice可以选择发送ItemSuccessfullyCreated或处理其他失败案例。然后在ITemSuccessfullyCretaed之后,将价格添加到发票。

    FLOW _ 2

    1. 创建一个Invoice聚合,其中所有项目ID和状态已初始化并输出多个事件 - 一个InvoiceCreated(包含所有项目ID)
    2. 某人(ProcessManager)侦听InvoiceCreated事件,并在某个表中创建另一个Aggregate或者将invoiceid与项目vs状态的列表放在一起。然后在事件检查剩余项目ID并将其分配给发票并以与上述相同的方式更新发票,或者也可以更新价格。可能将其分解为多个更有意义来更好地管理代码。
    3. 您可以想到其他流量。您可以继续阅读http://microservices.io/了解更多此类模式

答案 1 :(得分:1)

  

我天真的想法,来自与我一起工作的悠久历史   非分布式关系数据库,就是整体而已   同情交易的情况蔓延到两者   聚集。但该框架似乎并不支持这个

是的,在一次交易中写入多个事件流通常不是好的做法。你还有几个选择:

检查然后创建:"顺序"同步一致性

由于我们正在谈论聚合创建,您可以按顺序,以同步方式执行不变检查(A)和本身发票创建(B)但不使用交易。由于发票在B完成之前不存在,因此不存在并发访问它的风险。因为A本身就是原子的,所以你已经找到了所有可能的并发案例。在做B之前,你必须检查A没有出现任何问题。万一B失败,只需记录错误或发送通知。

  • 如果您可以在您的域中找到合适的概念,请设计一个能够强制执行A​​的聚合 - 基本上它应该包含项目地图及其相应的发票。如果你设法回答问题"它在哪个范围内保持不变,通常效果很好? (只有一个客户?一家公司?还有其他什么?)"并围绕该范围设计聚合。加载聚合,让它检查不变量,如果可以,则在其中注册新的项目/发票关联,并产生新的发票聚合。

  • 设计聚合,使得持久存储在技术上能够强制执行唯一性不变量。例如,以Item.Invoiced.(ItemId)为键的事件流。在创建发票流的路径上,这可以被视为interim stream

稍后创建并检查:最终一致性

  • InvoiceCreated上,尝试将新项目/发票关联插入到具有(itemId)唯一约束的表中,或使用上面提到的代理事件流。出于实际原因,该表可以与您的读取模型位于同一数据库中。如果插入失败,则触发补偿操作(从发票中删除项目,取消发票等)