【问题标题】:Importing data and Event Sourcing导入数据和事件溯源
【发布时间】:2021-07-18 18:10:10
【问题描述】:

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

目前的流程是:

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

对此的其他详细信息是多行可能属于同一实体(例如,2 行可能是订单中的行项目,因此具有相同的 Order Ref)。

我正在考虑有一个导入传奇,它会生成一堆导入聚合(例如 OrderImportAggregate),然后在提交导入时,这些将被转换为当前在系统中使用的类,这有望成为聚合重新设计时,他们自己的权利更进一步!因此,传奇过程将采取以下方式:

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

由于这是我第一次实现 CQRS/事件溯源/DDD,我想从正确的基础开始,所以想知道这是否是实现此功能的理想方法?

【问题讨论】:

  • 在整个问题中重复出现的“[EntityType]”是什么?
  • 有多种类型可以导入。在不放弃我实际工作的情况下,我能想到的最接近的事情就是能够将客户和订单从在线商店导入一些后台系统。实体类型是客户和订单。
  • 好的,你的聚合保护的不变量是什么?
  • 他们将在遗留代码中强制执行当前类的不变量,例如客户必须有超过一定年龄的出生日期、有效姓名等。但也会保留验证错误(如果有)以及警告,例如警告实体已存在于系统中。这些不适合客户/订单等当前遗留类的域。
  • 我正在尝试了解旅游业务以及此 import process 的意义以及它与这些聚合的关系。

标签: domain-driven-design cqrs event-sourcing


【解决方案1】:

我建议您将您的域分成两个独立的子域,以实现单独的有界上下文,一个有界上下文是 Import bounded context (ImportBC),另一个是 receiving bounded context (ReceivingBC,我不知道实际名称,请相应地替换它。

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

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

【讨论】:

  • 这听起来像是一种合理的方法,我认为这与我的想法相似,但我认为我第一次过于热衷于实施现代建筑,从而试图过度设计事物!感谢您让我回到地球并为我指明正确的方向!
【解决方案2】:

首先,您必须问自己为什么要使用 CQRS。 CQRS 是建筑中最重的 18 轮车。我知道 2 个让 CQRS 尖叫的好理由

1) 你需要支持撤销功能

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

但是,您所描述的部分需求感觉非常像粗鲁。 (您导入一组行,列出一组行,编辑这些行,然后将标记为已完成的行从其输入状态中删除并转换为其他类型的实体。

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

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

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

【讨论】:

  • 我了解 CQRS 和 Event Sourcing 之间的区别,但我的方法可能会搅浑水。了解该实体是如何形成和更改的历史将是可取的,并允许撤消功能(尽管可能不适用于此特定上下文)。正如Constantin GALBENU 所提到的,我觉得进口/出口的有限上下文将是一个好方法。我想我过于渴望将现代建筑引入一大堆泥巴中,并试图过度设计,所以我会重新考虑!
  • “我知道 2 个让 CQRS 尖叫的好理由 1) 您需要支持撤消功能 2) 在未来实施新要求时,您也希望将这些应用到过去的数据中。”不,那是事件采购的两个很好的理由; CQRS 只是将您的读取与写入分开。
  • true,但 cqrs 通常是通过事件/事件源实现的。如果您只是在单个数据库上将读取与写入分开,那么我看不到 cqrs 的优势
【解决方案3】:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-27
    • 2019-01-31
    • 2018-02-17
    • 2021-07-25
    • 1970-01-01
    • 2018-06-24
    相关资源
    最近更新 更多