验证联系人在 Axon 中具有唯一的电子邮件

Validating a Contact has a Unique Email in Axon

我很想知道在使用 Axon 框架验证电子邮件字段对于联系人聚合的一组电子邮件是唯一的时,最佳实践方法是什么。

示例设置

ContactCreateCommand {
   identifier = '123'
   name = 'ABC'
   email = 'info@abc.com'
}

ContactAggregate {
   ContactAggregate(ContactCreateCommand cmd) {
      //1. cannot validate email
      AggregateLifecycle.apply(
                new ContactCreatedEvent(//fields ... );
        );
   }
}

根据我对这可能如何实施的理解,我已经确定了多种可能的处理方法,但也许还有更多。

1.在 Aggregate

中什么都不做

这种方法强制要求(命令的)调用者在发送命令之前通过电子邮件查询联系人,允许几毫秒,最终一致性允许重复。

缺点:

2。在单独的持久层中验证

这种方法引入了一个新的持久层,可以验证聚合内部的唯一性。

ContactAggregate 命令处理程序中 ContactCreateCommand 然后我们可以针对这个持久层发出查询(例如 table 在带有唯一索引的 postgres 中),我们可以根据包含所有集合的数据库验证电子邮件

缺点:

3。使用 Saga 和单例聚合

此方法通过引入最多只能有 1 个实例的聚合(例如,目标标识符始终相同)来增强以前的设置。这样我们创建了一个 'Singleton Aggregate' 只负责封装所有联系人电子邮件地址的集合。

ContactEmailValidateCommand {
   identifier = 'SINGLETON_ID_1'
   email='info@abc.com'
   customerIdentifier = '123'
}
UniqueContactEmailAggregate {
   @AggregateIdentifier
   private String identifier;

   Set<String> email = new HashSet<>();
   
   on(ContactEmailValidateCommand cmd) {
     if (email.contains(cmd.email) == false) {
       AggregateLifecycle.apply(
                new ContactEmailInvalidatedEvent(//fields ... );
     } else {
       AggregateLifecycle.apply(
                new ContactEmailValidatedEvent(//fields ... );
        );
     } 
   } 
   
}

完成此检查后,我们可以对 ContactEmailInvalidatedEventContactEmailValidatedEvent 重新采取适当的行动,这可能会导致之后的联系无效。

这种方法的好处是它保持聚合本地的持久性,这可以提供更好的缩放(随着添加更多节点,存在更多具有本地管理集的聚合)。

缺点

其他人如何解决这个问题?我觉得选项 2 可能是最简单的方法,但是还有其他选项吗?

您本质上要寻找的是基于集的验证(我认为 here 博客很好地解释了这个概念,以及如何在 Axon 中处理它)。简而言之,验证某个字段包含(或不包含)在一组数据中。在执行 CQRS 时,这会成为一个有点有趣的概念来推理,有几种解决方案(正如您已经描绘的那样)。

我认为最好的解决方案总结在您的第二个选项下,即为电子邮件地址使用专用的持久层。您只需创建一个仅包含电子邮件地址的非常简洁的模型,您将在发布 ContactCreateCommand 之前对其进行验证。请注意,此持久层属于命令模型,因为它用于执行业务验证。因此,您将介绍一个示例,其中您的命令模型中不仅有聚合,还有视图。正如您正确指出的那样,这个视图当然需要针对它的用例进行优化。也许引入一个在应用程序启动时创建的缓存不会太糟糕。

为确保此电子邮件地址视图尽可能最新,最明智的做法是确保它在与 ContactCreatedEvent(我假设包含新电子邮件地址)时相同的事务中更新出版。您可以通过为您的“电子邮件地址视图”设置一个专用的事件处理组件来做到这一点,该组件通过 SubscribingEventProcessor(SEP)进行更新。这将起作用,因为 SEP 由发布事件(您的聚合)的同一线程调用。

在发送命令之前查询此模型时,您有多种选择。例如,您可以使用仅对 ContactCreateCommand 做出反应的 MessageDispatchInterceptor。或者,您引入一个 Handler Enhancer 专门用于反应 ContactCreateCommand 以执行此验证。或者,您引入另一个命令,例如 RequestContactCreationCommand,它针对常规组件。该组件将处理命令、验证模型,如果获得批准,则发送 ContactCreateCommand.

这是我对这种情况的两分钱,希望这对@vcetinick 有所帮助!