Warm tip: This article is reproduced from serverfault.com, please click

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

(java - Axon: Create and Save another Aggregate in Saga after creation of an Aggregate)

发布于 2020-11-28 16:35:13

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

我有2个服务:

ProductManagementService使用产品详细信息保存一个Product实体)

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

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

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

我想做的事:

在ProductManagementService中创建新产品时,我想在此后直接在ProductInventoryService中创建一个ProductInventory实体,并将其保存到我的msql表中。新的ProductInventory实体应具有与Product实体相同的ID

为此,我创建了一个Saga,该Saga列出了ProductCreatedEvent并发送了一个新的CreateProductInventoryCommand。一旦CreateProductInventoryCommand触发一个ProductInventoryCreatedEvent,如2.)中所述的EventHandler应该捕获它。除非不是。

保存的唯一东西是产品实体,因此总而言之

1.)有效,2.)无效。确实创建了ProductInventory Aggregate,但由于未触发连接到EventHandler的保存过程,因此未保存它。

我也收到异常,但是应用程序不会崩溃: Command 'com.myApplication.apicore.command.CreateProductInventoryCommand' resulted in org.axonframework.commandhandling.CommandExecutionException(OUT_OF_RANGE: [AXONIQ-2000] Invalid sequence number 0 for aggregate 3cd71e21-3720-403b-9182-130d61760117, expected 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;
    }

}
Questioner
Jan
Viewed
0
Steven 2020-11-30 20:36:55

你现在注意到的是给定事件存储中[集合标识符,序列号]对的唯一性要求。之所以有此要求,是为了保护你免于在同一聚合实例上进行潜在的并发访问,因为同一聚合的多个事件都需要具有唯一的整体序列号。此数字还用于标识需要处理事件的顺序,以确保以一致的顺序一致地重新创建聚合。

因此,你可能会认为这会选择“很抱歉,没有适当的解决方案”,但是幸运的是事实并非如此。在此设置中,大约可以做三件事:

  1. 具有两种聚合的事实将具有唯一的标识符。
  2. 在两个应用程序之间使用不同的有界上下文。
  3. 更改聚合标识符的写入方式。

选项1可以说是最务实的,也是大多数人使用的选项。但是,你已经注意到必须重复使用标识符,因此我假设你已经完全忽略了此选项。无论如何,我都会尝试重新使用这种方法,因为UUID对于创建的每个新实体,默认情况下都使用s可以避免将来遇到麻烦。

选项2可以用DDD引入的Bounded Context概念来反映自己Product聚合和ProductInventory聚合驻留在不同的上下文中将意味着你将为两者拥有不同的事件存储。因此,将保持唯一性约束,因为没有单个存储包含两个聚合事件流。但是,此方法是否可行取决于两个聚合是否实际上属于同一上下文(是/否)。在这种情况下,你可以例如使用Axon Server的多上下文支持来创建两个不同的应用程序。

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

从我的角度出发,很难推断出这三个选项中哪个更适合你的解决方案。我所做的就是从我的角度最合理地订购它们。希望这对你进一步@Jan有帮助!