Validation 如何在CQR中处理基于集合的一致性验证?

Validation 如何在CQR中处理基于集合的一致性验证?,validation,domain-driven-design,cqrs,eventual-consistency,Validation,Domain Driven Design,Cqrs,Eventual Consistency,我有一个相当简单的域模型,其中包含一个Facility聚合根列表。鉴于我正在使用CQR和事件总线来处理从域引发的事件,您如何处理集合上的验证?例如,假设我有以下要求: 设施必须具有唯一名称 因为我在查询端使用的是最终一致的数据库,所以不能保证在事件处理或处理事件时数据库中的数据是准确的 例如,FacilityCreatedEvent位于查询数据库事件处理队列中,等待处理并写入数据库。新的CreateFacilityCommand将发送到要处理的域。域服务查询read数据库,查看是否有任何其他已使

我有一个相当简单的域模型,其中包含一个
Facility
聚合根列表。鉴于我正在使用CQR和事件总线来处理从域引发的事件,您如何处理集合上的验证?例如,假设我有以下要求:

  • 设施
    必须具有唯一名称
  • 因为我在查询端使用的是最终一致的数据库,所以不能保证在事件处理或处理事件时数据库中的数据是准确的


    例如,
    FacilityCreatedEvent
    位于查询数据库事件处理队列中,等待处理并写入数据库。新的
    CreateFacilityCommand
    将发送到要处理的域。域服务查询read数据库,查看是否有任何其他已使用该名称注册的
    设施
    ,但返回false,因为
    CreateNewFacilityEvent
    尚未处理并写入存储。新的
    CreateFacilityCommand
    现在将成功,并抛出另一个
    FacilityCreateEvent
    ,当事件处理器试图将其写入数据库时,发现另一个同名的
    设施已经存在。

    我采用的解决方案是添加一个
    系统
    聚合可以维护当前
    设施
    名称列表的根目录。创建新的
    设施
    时,我使用
    系统
    聚合(只有一个
    系统
    作为全局对象/单个对象)作为工厂。如果给定的设施名称已经存在,那么它将抛出验证错误


    这将使验证约束保持在域内,并且不依赖于最终一致的查询存储。

    在这种情况下,您可以实现一个简单的CRUD样式的服务,该服务基本上使用主键约束在Sql表中进行插入

    插入只会发生一次。当具有相同值且只应存在一次的重复命令命中聚合时,聚合调用服务,服务由于违反主键约束而导致插入操作失败,抛出错误,整个过程失败,并且没有生成事件,查询端没有报告,可能是在表中报告故障,以便最终进行一致性检查,用户可以通过查询了解命令处理的状态。要检查这一点,只需使用命令Guid反复查询命令状态视图模型

    显然,当该命令包含一个主键检查表中不存在的值时,该操作是成功的


    主键约束表应仅用作服务,但由于您实施了事件源,因此可以重播事件以重建主键约束表。

    中概述了三种方法:

  • 如果问题很少见或不重要,可以通过向管理员发送通知的方式进行管理处理
  • 调度DuplicateFacilityNameDetected事件,这可能启动自动解决过程
  • 维护一个知道所用设施名称的服务,可能通过监听域事件和维护一个持久的名称列表。在创建任何新设施之前,请先检查此服务

  • 另请参见此相关问题:

    因为唯一性检查将在数据写入之前完成,因此更好的方法是构建事件跟踪服务,该服务将在进程完成或终止时发送通知。

    您在解决方案的命令端使用哪种类型的存储?经典ORM或事件源?我知道这是一个老问题,但您的方法意味着CreateFacility命令处理程序实际上修改了两个聚合,“全局”系统聚合和新创建的设施聚合。还是我误解了?@user1420752您不能“修改”刚刚创建的内容;)在创作上没有潜在的争论。好吧,措辞拙劣。他修改了全球“系统”总量,同时创建了新的“设施”总量。如果在一个事务中修改两个聚合(例如,如果您在符合ACID的数据库上使用“传统”DDD),则没有问题,但CQRS“纯粹主义者”会说“聚合表示事务一致的边界!您限制了可伸缩性!”。但是如果你不需要高可伸缩性(我相信大多数人都不需要),那么上面的解决方案就可以了。如果(3)中的服务监听域事件,它最终不是会保持一致吗?前一个命令可能已完成,但在处理下一个命令之前,服务无法接收和处理该事件。