在 CQRS 系统中对数千个聚合进行批量操作,人们会这样做吗?

Nik*_*žić 6 domain-driven-design cqrs event-sourcing microservices

我正在处理用于管理银行信用卡的应用程序。该应用程序选择了 CQRS 和事件溯源架构。应用程序中最重要的聚合是CreditCard控制信用卡生命周期。它看起来像:

class CreditCard {
   private int status;
   public void activate() {...}
   public void deactivate () {...}
   ...
}
Run Code Online (Sandbox Code Playgroud)

如果方法调用成功,它的activatedeactivate方法分别保护信用卡不变量和发布CardActivatedEventCardDeactivatedEvent。我们将这些事件存储在事件存储中,以便稍后在命令端进行聚合重建。我们将这些事件应用于各种视图。我们使用这些事件通知其他第三方系统。目前一切都很好。

最近,我们收到了一项新要求,即按月向所有有效信用卡收费。

我的第一直觉是,好吧,我们可以将charge方法添加到同一个CreditCard聚合中。该方法可以检查一些与充电相关的不变量。例如,卡是否处于正确的收费状态,是否已经收费等。成功调用后,此方法可以发布CardChargedEvent。然后我们可以创建一些流程管理器,它会每月查询一次活动信用卡的视图端以获取它们的 ID。有了这些 ID,流程管理器可以向命令端发出多个收费命令(每个信用卡聚合一个)。对于收到的每个充电命令,命令端将重建CreditCard聚合对象并调用它charge方法。唯一的问题是这种方法看起来效率很低。特别是关于命令端的数据库往返(每个聚合实例一次读取和一次写入)。如果我们考虑到我们可以轻松地在我们的应用程序中拥有 10 万多张信用卡的等式,这个往返开销开始在我看来有点问题。

有没有人对 CQRS/ES 系统上的批处理操作有任何经验?我的担心有效吗?遇到这种情况怎么办?您如何在 CQRS 系统中实现批处理?

我想到的另一种选择是,对于收费用例,我放弃了 CQRS/ES/DDD 原则,并在我们的一个视图数据库上使用存储过程来实现整个过程。这个过程可以在信用卡视图表中搜索合适的信用卡,并用找到的记录填充“待收费队列”表。然后我可以有一些外部进程读取第二个表并执行它需要做的任何事情。

Arw*_*nFr -1

最近,我们收到了一项新要求,要求按月向所有有效信用卡收取费用。我的第一直觉是,好吧,我们可以将收费方法添加到同一个信用卡聚合中。

我认为这就是设计缺陷发生的地方。

您的CreditCard聚合在设计时考虑了特定的用例:

应用程序中最重要的聚合是信用卡,它控制信用卡生命周期。

信用卡收费不属于信用卡生命周期的一部分。是否发生取决于信用卡状态,但(成功)信用卡收费不会更改域对象的状态。它不应与信用卡域聚合交互,因为聚合的目的是在更改状态时强制执行业务规则。您应该问自己:信用卡收费时发生了哪些总量变化?

这个问题的答案取决于您的域模型和业务案例的其余部分,但它与帐户余额或信用授权等内容的关系比卡本身更大。你可以这样实现:

  • 每月运行的批处理过程将查询您的 CreditCard 聚合中是否有活动卡,然后尝试通过向 AccountBalance 聚合发送命令来向所有客户收取每月费用;

  • 如果客户有足够的钱,AccountBalance 聚合将引发 BalanceChangedEvent,如果没有,则引发 CreditAuthorizationRequiredEvent,暂时冻结帐户,直到信用被授权或拒绝;

  • CreditAuthorization 聚合可以根据信用额度业务规则允许或拒绝信用,并相应地引发事件;

  • AccountBalance 聚合将解冻帐户,根据结果更改或不更改余额,最终引发或不引发 BalanceChangedEvent ;

  • CreditCard 聚合将注册到 CreditDeniedEvent 以停用信用卡,因为客户无法支付费用;

... 等等 ...