Java 防止重复AggregateCreated事件的最佳做法

Java 防止重复AggregateCreated事件的最佳做法,java,cqrs,axon,Java,Cqrs,Axon,我有以下(轴突)聚合: @Aggregate @NoArgsConstructor public class Car{ @AggregateIdentifier private String id; @CommandHandler public Car(CreateCar command){ apply( new CarCreated(command.getId()) ); } @EventSourcingHandler public void carCr

我有以下(轴突)聚合:

@Aggregate
@NoArgsConstructor
public class Car{
  @AggregateIdentifier
  private String id;

  @CommandHandler
  public Car(CreateCar command){
    apply( new CarCreated(command.getId()) );
  }

  @EventSourcingHandler
  public void carCreated(CarCreated event) {
    this.id = event.getId();
  }

}
我可以通过提交带有特定id的
CreateCar
命令来创建汽车,从而引发
CarCreated
事件。那太好了

但是,如果我发送另一个具有相同Id的
CreateCar
命令,则聚合无法验证该命令(给定Id已经存在)。随后,它将简单地触发一个新的
CarCreated
事件。这是个谎言

如果汽车已经存在,确保
CreateCar
命令失败的最佳方法是什么

当然,我可以先检查存储库,但这不会阻止竞争条件

如果汽车已经存在,确保CreateCar命令失败的最佳方法是什么?当然,我可以先检查存储库,但这不会阻止竞争条件

没有魔法

如果要避免快速写入,则需要获取数据存储上的锁,或者需要具有
比较和交换
语义的数据存储

使用锁,可以保证在读取存储中的数据和随后的写入之间不会发生冲突更新

lock = lock_for_id id
lock.acquire
Try:
    Option[Car] root = repository.load id
    switch root {

        case None:
            Car car = createCar ...
            repository.store car  

        case Some(car):
            // deal with the fact that the car has already been created   

    }
Finally:
    lock.release
您希望每个聚合都有一个锁,但是创建锁与创建聚合具有相同的racy条件。因此,您很可能最终会遇到类似的问题来限制对操作的访问

通过比较和交换,您可以将竞争管理推向数据存储。您发送的不是商店a,而是

我们不再需要锁,因为我们正在为存储精确地描述需要满足的前提条件(例如)

事件存储通常支持比较和交换语义;将新事件“附加”到流上是通过创建一个查询来完成的,该查询标识尾部指针的预期位置,并使用特殊编码的值来标识预期创建流的情况(例如,事件存储支持语义)

但是,如果我发送另一个具有相同Id的CreateCar命令,则聚合无法验证该命令(给定Id已经存在)。随后,它将简单地触发一个新的CarCreated事件。这是个谎言

Axon实际上会帮你解决这个问题。当聚合发布事件时,它不会立即发布到其他组件。它在工作单元中分段,等待处理程序执行完成。 处理程序执行后,将调用许多“准备提交”处理程序。其中一个存储聚合(在使用事件源时为no-op),另一个存储事件的发布(在事务范围内)


根据是否使用事件源,将聚合实例添加到持久性存储器将失败(重复键),或者发布创建事件将失败(重复聚合标识符+序列号).

在数据库中使用UUID或generate id,这如何防止两个CarCreated事件?具有相同聚合标识符的相同创建事件无法保存在事件存储中,因此此事务应在数据库级别失败,并回滚。我怀疑您正在使用UUID作为聚合标识符,并且实际上希望聚合在另一个标识符上也具有唯一约束。在本例中,就axon而言,您正在创建一个新的唯一聚合,它恰好与另一个聚合具有相同的负载。您必须通过锁定并读取查询表来解决此问题。这通常使用乐观并发处理。向存储技术发送事件时,应随事件一起发送预期的事件索引。对于您创建的事件,该事件将为0(对于基于0的系统)。您的存储技术有望支持这一点,并将检查索引为0的事件是否尚未存储,否则将失败。通过对骨料进行水合处理,可以获得当前事件指数。请看,当您为聚合获取事件时(在处理命令之前),您只需计算事件的数量,并使用该数量作为预期的事件索引
    Option[Car] root = repository.load id
    switch root {

        case None:
            Car car = createCar ...
            repository.replace car None

        case Some(car):
            // deal with the fact that the car has already been created   

    }