Java Axon:创建聚合后,在Saga中创建并保存另一个聚合

Java Axon:创建聚合后,在Saga中创建并保存另一个聚合,java,spring-boot,event-handling,event-sourcing,axon,Java,Spring Boot,Event Handling,Event Sourcing,Axon,更新:问题似乎是我使用了两次的id,或者换句话说,是我想要用于productinventory实体的产品实体的id。一旦我为productinventory实体生成了一个新id,它似乎就可以正常工作了。但我想两者都有相同的id,因为它们是相同的产品 我有两项服务: ProductManagementService(使用产品详细信息保存产品实体) 1.)为了保存产品实体,我实现了一个EventHandler,它侦听ProductCreatedEvent并将产品保存到mysql数据库 Product

更新:问题似乎是我使用了两次的id,或者换句话说,是我想要用于productinventory实体的产品实体的id。一旦我为productinventory实体生成了一个新id,它似乎就可以正常工作了。但我想两者都有相同的id,因为它们是相同的产品

我有两项服务:

ProductManagementService(使用产品详细信息保存产品实体)

1.)为了保存产品实体,我实现了一个EventHandler,它侦听ProductCreatedEvent并将产品保存到mysql数据库

ProductInventoryService(将具有产品库存量的ProductInventory实体保存到ProductManagementService中定义的特定productId)

2.)为了保存ProductInventory实体,我还实现了一个EventHandler,它侦听ProductInventoryCreatedEvent并将产品保存到mysql数据库

我想做什么:

在ProductManagementService中创建新产品时,我希望直接在ProductInventoryService中创建ProductInventory实体,并将其保存到我的msql表中。新产品库存实体应具有与产品实体相同的id

为了实现这一点,我创建了一个Saga,它将列出ProductCreatedEvent并发送一个新的CreateProductInventoryCommand。一旦CreateProductInventoryCommand触发ProductInventoryCreatedEvent,EventHandler(如2.)中所述)就会捕获它。但事实并非如此

thta保存的唯一内容是产品实体,因此,总而言之

1.)有效,2.)无效。确实会创建ProductInventory聚合,但不会保存,因为未触发连接到EventHandler的保存过程

我还遇到一个异常,但应用程序没有崩溃:
Command'com.myApplication.apicore.Command.CreateProductInventoryCommand'导致org.axonframework.commandhandling.CommandExecutionException(超出范围:[AXONIQ-2000]聚合3cd71e21-3720-403b-9182-130d61760117的序列号0无效,应为1)

我的传奇:

@Saga
@ProcessingGroup("ProductCreationSaga")
public class ProductCreationSaga {

    @Autowired
    private transient CommandGateway commandGateway;

    @StartSaga
    @SagaEventHandler(associationProperty = "productId")
    public void handle(ProductCreatedEvent event) {
        System.out.println("ProductCreationSaga, SagaEventHandler, ProductCreatedEvent");

        String productInventoryId = event.productId;
        SagaLifecycle.associateWith("productInventoryId", productInventoryId);
    //takes ID from product entity and sets all 3 stock attributes to zero
        commandGateway.send(new CreateProductInventoryCommand(productInventoryId, 0, 0, 0));
    }

    @SagaEventHandler(associationProperty = "productInventoryId")
    public void handle(ProductInventoryCreatedEvent event) {
        System.out.println("ProductCreationSaga, SagaEventHandler, ProductInventoryCreatedEvent");

        SagaLifecycle.end();
    }

}
按预期工作并保存产品实体的EventHandler:

@Component
public class ProductPersistenceService {

    @Autowired
    private ProductEntityRepository productRepository;

    //works as intended
    @EventHandler
    void on(ProductCreatedEvent event) {
        System.out.println("ProductPersistenceService, EventHandler, ProductCreatedEvent");
        ProductEntity entity = new ProductEntity(event.productId, event.productName, event.productDescription, event.productPrice);
        productRepository.save(entity);

    }

    @EventHandler
    void on(ProductNameChangedEvent event) {
        System.out.println("ProductPersistenceService, EventHandler, ProductNameChangedEvent");
        ProductEntity existingEntity = productRepository.findById(event.productId).get();

        ProductEntity entity = new ProductEntity(event.productId, event.productName, existingEntity.getProductDescription(), existingEntity.getProductPrice());
        productRepository.save(entity);

    }
}
应保存ProductInventory实体但不保存的EventHandler:

@Component
public class ProductInventoryPersistenceService {

    @Autowired
    private ProductInventoryEntityRepository productInventoryRepository;

    //doesn't work
    @EventHandler
    void on(ProductInventoryCreatedEvent event) {
        System.out.println("ProductInventoryPersistenceService, EventHandler, ProductInventoryCreatedEvent");
        ProductInventoryEntity entity = new ProductInventoryEntity(event.productInventoryId, event.physicalStock, event.reservedStock, event.availableStock);
        System.out.println(entity.toString());

        productInventoryRepository.save(entity);

    }

}
产品合计:

@Aggregate
public class Product {

    @AggregateIdentifier
    private String productId;

    private String productName;

    private String productDescription;

    private double productPrice;

    public Product() {
    }

    @CommandHandler
    public Product(CreateProductCommand command) {
        System.out.println("Product, CommandHandler, CreateProductCommand");
        AggregateLifecycle.apply(new ProductCreatedEvent(command.productId, command.productName, command.productDescription, command.productPrice));
    }

    @EventSourcingHandler
    protected void on(ProductCreatedEvent event) {
        System.out.println("Product, EventSourcingHandler, ProductCreatedEvent");

        this.productId = event.productId;
        this.productName = event.productName;
        this.productDescription = event.productDescription;
        this.productPrice = event.productPrice;
    }

}
@Aggregate
public class ProductInventory {

    @AggregateIdentifier
    private String productInventoryId;

    private int physicalStock;

    private int reservedStock;

    private int availableStock;

    public ProductInventory() {
    }


    @CommandHandler
    public ProductInventory(CreateProductInventoryCommand command) {
        System.out.println("ProductInventory, CommandHandler, CreateProductInventoryCommand");
        AggregateLifecycle.apply(new ProductInventoryCreatedEvent(command.productInventoryId, command.physicalStock, command.reservedStock, command.availableStock));
    }

    @EventSourcingHandler
    protected void on(ProductInventoryCreatedEvent event) {
        System.out.println("ProductInventory, EventSourcingHandler, ProductInventoryCreatedEvent");

        this.productInventoryId = event.productInventoryId;
        this.physicalStock = event.physicalStock;
        this.reservedStock = event.reservedStock;
        this.availableStock = event.availableStock;
    }

}
产品库存合计:

@Aggregate
public class Product {

    @AggregateIdentifier
    private String productId;

    private String productName;

    private String productDescription;

    private double productPrice;

    public Product() {
    }

    @CommandHandler
    public Product(CreateProductCommand command) {
        System.out.println("Product, CommandHandler, CreateProductCommand");
        AggregateLifecycle.apply(new ProductCreatedEvent(command.productId, command.productName, command.productDescription, command.productPrice));
    }

    @EventSourcingHandler
    protected void on(ProductCreatedEvent event) {
        System.out.println("Product, EventSourcingHandler, ProductCreatedEvent");

        this.productId = event.productId;
        this.productName = event.productName;
        this.productDescription = event.productDescription;
        this.productPrice = event.productPrice;
    }

}
@Aggregate
public class ProductInventory {

    @AggregateIdentifier
    private String productInventoryId;

    private int physicalStock;

    private int reservedStock;

    private int availableStock;

    public ProductInventory() {
    }


    @CommandHandler
    public ProductInventory(CreateProductInventoryCommand command) {
        System.out.println("ProductInventory, CommandHandler, CreateProductInventoryCommand");
        AggregateLifecycle.apply(new ProductInventoryCreatedEvent(command.productInventoryId, command.physicalStock, command.reservedStock, command.availableStock));
    }

    @EventSourcingHandler
    protected void on(ProductInventoryCreatedEvent event) {
        System.out.println("ProductInventory, EventSourcingHandler, ProductInventoryCreatedEvent");

        this.productInventoryId = event.productInventoryId;
        this.physicalStock = event.physicalStock;
        this.reservedStock = event.reservedStock;
        this.availableStock = event.availableStock;
    }

}

您现在注意到的是给定事件存储中[aggregate identifier,sequence number]对的唯一性要求。此要求是为了防止对同一聚合实例的潜在并发访问,因为同一聚合的多个事件都需要具有唯一的总序列号。此外,此数字还用于标识事件需要处理的顺序,以确保以相同的顺序一致地重新创建聚合

因此,您可能认为这会选择“抱歉,没有解决方案”,但幸运的是,情况并非如此。在此设置中,您可以大致执行三项操作:

  • 事实上,两个聚合都将具有唯一标识符
  • 在两个应用程序之间使用不同的有界上下文
  • 更改写入聚合标识符的方式
  • 选项1可以说是最务实的,并且被大多数人使用。然而,您已经注意到重用标识符是必要的,所以我假设您已经完全忽略了这一选项。无论如何,我会尝试重新讨论这种方法,因为为您创建的每个新实体使用默认的
    UUID
    s可以避免您将来遇到麻烦

    选项2将通过引入的有界上下文概念反映自身。让
    产品
    聚合和
    ProductInventory
    聚合驻留在不同的上下文中,这意味着您将为两者提供不同的事件存储。因此,将保留唯一性约束,因为没有单个存储同时包含两个聚合事件流。然而,这种方法是否可行取决于两个聚合是否真正属于相同的上下文是/否。如果是这种情况,您可以使用Axon服务器创建两个不同的应用程序

    选项3需要了解Axon的功能。当它存储事件时,它将调用聚合中带注释的
    @AggregateIdentifier
    字段上的
    toString()
    方法。由于
    @AggregateIdentifier
    注释字段是
    字符串
    ,因此将按原样为您提供标识符。您可以做的是使用类型化标识符,
    toString()
    方法不会只返回标识符,而是将聚合类型附加到它。这样做将使存储的
    aggregateIdentifier
    唯一,而从使用角度看,您似乎仍然在重用标识符

    从我的角度来看,很难推断这三个选项中哪一个更适合您的解决方案。从我的角度来看,我所做的是以最合理的方式排列它们。
    希望这将有助于你的进一步@Jan

    非常感谢您的详细和有益的回答!