AxonFramework状态存储聚合
在聚合篇中,我们看到了如何创建由事件溯源支持的聚合。 换句话说,事件溯源聚合的存储方法是通过重放构成聚合变化的事件。
然而,聚合也可以按原样存储。 这样做时,用于保存和加载聚合的 Repository 是 GenericJpaRepository 。 聚合状态存储的结构与事件溯源聚合有点不同:import org.axonframework.commandhandling.CommandHandler; import org.axonframework.eventhandling.EventHandler; import org.axonframework.modelling.command.AggregateIdentifier; import org.axonframework.modelling.command.AggregateMember; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; @Entity // 1. public class GiftCard { @Id // 2. @AggregateIdentifier private String id; // 3. @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn(name = "giftCardId") @AggregateMember private List transactions = new ArrayList<>(); private int remainingValue; @CommandHandler // 4. public GiftCard(IssueCardCommand cmd) { if (cmd.getAmount() <= 0) { throw new IllegalArgumentException("amount <= 0"); } id = cmd.getCardId(); remainingValue = cmd.getAmount(); // 5. apply(new CardIssuedEvent(cmd.getCardId(), cmd.getAmount())); } @CommandHandler public void handle(RedeemCardCommand cmd) { // 6. if (cmd.getAmount() <= 0) { throw new IllegalArgumentException("amount <= 0"); } if (cmd.getAmount() > remainingValue) { throw new IllegalStateException("amount > remaining value"); } if (transactions.stream().map(GiftCardTransaction::getTransactionId).anyMatch(cmd.getTransactionId()::equals)) { throw new IllegalStateException("TransactionId must be unique"); } // 7. remainingValue -= cmd.getAmount(); transactions.add(new GiftCardTransaction(id, cmd.getTransactionId(), cmd.getAmount())); apply(new CardRedeemedEvent(id, cmd.getTransactionId(), cmd.getAmount())); } @EventHandler // 8. protected void on(CardReimbursedEvent event) { this.remainingValue += event.getAmount(); } protected GiftCard() { } // 9. }
上面的摘录显示了来自 "Gift Card Service" 的状态存储聚合。 片段中的编号注释指出了 Axon 的细节,在此解释: 由于聚合存储在 JPA 存储库中,因此需要使用 @Entity 注解类。聚合根必须声明一个包含聚合标识符的字段。
此标识符最迟必须在第一个事件发布时初始化。
此标识符字段必须由 @AggregateIdentifier 注解进行注解。
当使用 JPA 存储 Aggregate 时,Axon 知道使用 JPA 提供的 @Id 注解。
由于 Aggregate 是一个实体,@Id 注解是一个硬性要求。这个聚合有几个 "Aggregate Members"。
由于聚合按原样存储,因此应考虑实体的正确映射。 带 @CommandHandler 注解的构造函数,或者以不同的方式放置 "command handling constructor"。
此注解告诉框架给定的构造函数能够处理 IssueCardCommand 。静态的 AggregateLifecycle#apply(Object…) 可用于发布 Event Message 。
调用此函数后,提供的 Object 将在应用它们的聚合范围内作为 EventMessage 发布。命令处理方法将首先决定此时传入的命令是否有效处理。 业务逻辑验证后,可以调整聚合的状态 Aggregate 中的实体可以通过定义 @EventHandler 注解方法来监听 Aggregate 发布的事件。
当事件消息在被任何外部处理程序处理之前发布时,将调用这些方法。JPA 需要的无参数构造函数。
未能提供此构造函数将在加载聚合时导致异常。
在命令处理程序中调整状态
与事件溯源聚合不同,状态存储聚合可以在命令处理程序中配对决策逻辑和状态更改。 遵循这种范式对状态存储聚合没有任何后果,因为没有事件溯源处理程序驱动它的状态。