导入数据和事件溯源

Importing data and Event Sourcing

我目前正在开发一个单体系统,我想将其引入现代并结合 DDD 和 CQRS。我收到了重写解决方案导入机制的请求,我觉得这可能是开始重新架构过程的好机会。

当前进程是:

  1. 用户上传 CSV
  2. 系统解析 CSV 并在屏幕上显示每一行。对每一行进行验证,并且 errors/warnings 与每一行相关联
  3. 用户可以修改每一行并重新验证所有行
  4. 然后用户选择没有错误的行并提交导入
  5. 行导入和任何未选择的行或有错误的行进入保留区域,以便他们可以在以后处理它们

其他详细信息是多行可以属于同一个实体(例如,2 行可以是订单中的行项目,因此会有相同的订单编号)。

我正在考虑有一个导入传奇,它会生成一堆导入聚合(例如 OrderImportAggregate),然后当导入被提交时,这些会被转换成当前在整个系统中使用的 class,当进一步重新构建时,它们有望成为自己的聚合体!所以 saga 过程将按照以下方式进行:

  1. [EntityType]FileImportUploaded - 存储 CSV
  2. [EntityType]FileImportParsed - 生成 n 个 [EntityType]Import 聚合。[EntityType]ImportItemCreated 事件 raised/handled
  3. Process 将调用当前实体通过的验证例程以生成错误列表(如果有)并针对每个项目进行存储。 [EntityType]ImportItemValidated 事件 raised/handled
  4. 每次在屏幕上更改一行时,它都会为 saga 和项目 ID 调用 Web api 方法来更新详细信息并根据第 3 点重新验证该行。
  5. 用户提交导入,服务将实体分组在一起,例如基于 ref,它们被转换为当前系统实体并调用它们的 import/save 例程。引发了 [EntityType]ImportItemCompleted 事件。
    1. 当所有聚合都处于 ImportItemComplete 状态时,Saga 完成

因为这是我第一次实现 CQRS/Event Sourcing/DDD,我想从正确的基础开始,所以想知道这是否是实现此功能所需的方法?

首先,您必须问问自己为什么要使用 CQRS。 CQRS 是架构中的重型 18 轮车。我知道尖叫 CQRS

的 2 个充分理由

1) 您需要支持撤消功能

2) 在未来实施新要求时,您也希望将这些应用到过去的数据中。

然而,您所描述的那部分要求感觉很像垃圾。 (导入一组行,列出一组行,编辑这些行,然后标记为已完成的行从其输入状态中删除并转换为其他类型的实体。

如果您觉得描述特定实体和适用的验证规则非常复杂,那么 DDD 将是一个不错的选择。但我仍然会考虑缩小它并构建一个简单的 mvc 风格的应用程序来实现这个(取决于这个项目的其他要求)

即使这是更大域的一部分,我也会建议采用微服务方法,其中这将是一个完全独立的导入应用程序(在这种情况下,您仍然可以引发 ImportCompleted 事件并将其放在服务总线上监听该事件的多个其他应用程序)

注意:CQRS 不是事件源,cqrs 将命令(更新)堆栈与查询堆栈分开。它通常与事件溯源结合使用。但是让随处弹出的事件维护起来会很痛苦,尤其是因为通常不太明显是谁在引发事件以及事件是否相互交互(如果同时引发订单完成事件和订单取消事件,订单会发生什么情况,可能与时间有关先处理哪一个的问题)

我建议您将您的域分成两个独立的子域,实现为单独的有界上下文,一个有界上下文是 Import bounded context (ImportBC),另一个是 receiving bounded contextReceivingBC,我不知道具体名字,请相应替换)。

然后,在 Import BC 中,您应该使用 CRUD 样式来实现,每个导入文件都有一个实体,并使用持久性来 记住 验证和导入过程的进展(该实体持有尚未导入的项目清单)。在人工验证每个项目后,可以向 ReceivindBC 中的聚合发送命令以根据业务规则测试聚合是否有效,但无需将更改提交到存储库!您这样做是为了让人类用户知道该项目是否确实有效,并 enable/disable 和 import button。这样,您就不会在两个有界上下文中复制验证逻辑。当用户实际按下 import button 时,将导入命令发送到 ReceivingBC 中的聚合,并且您实际上将更改提交到存储库。此外,您从 import file CRUD entity.

中删除导入项

这种发送命令但实际上不会持久保存到存储库中的技术有助于帮助 用户体验UI 中的(无需在 UI 中重复逻辑=]) 如果您遵循 DDD 最佳实践并将聚合设计为纯粹的、无副作用的对象(与存储库无关,不知道它们的存在,根本不使用它们),这是可行的!).

我不是 DDD 专家,但这是我处理这个问题的想法。我不会使用单独的限界上下文,因为在我看来,领域对象的导入理想情况下可以与它们所属的限界上下文处于同一限界上下文中。渴望听取专家的意见,为什么它会出错!

  1. 将 csv 解析为表示数据导入的聚合并将其持久化(到暂存区/表等)。将来我们可以从这里加载这个聚合。解析 CSV 文件以创建此聚合可以建模为命令“CreateDataImportFromCsvFile”等。
  2. 构建一个 UI 来加载并显示该聚合。聚合可以包含域对象列表“客户导入项目”,每个“客户导入项目”可以包含“IsSelected”属性 以及正在导入的域对象,即“客户”域对象本身。这意味着您不会在使用要导入的实际域对象时复制验证规则。您将这些对象水化并显示在 UI 中。当用户单击导入按钮时,您发出一个命令。您通过遍历聚合上每个选定的有效“导入项”并在其域模型上调用 Save() 来处理该命令,然后将导入项标记为已处理。理想情况下,这一切都在外部事务范围内完成(取决于您是否想要原子性与最终一致性等)。然后,您的 UI 可以选择不显示已处理的导入项目,或者它可以将它们显示为禁用状态或其他任何状态,具体取决于它是否对用户有用,还可以查看到目前为止实际处理的内容与剩余的内容.