简述
在本章中,我们将更详细地介绍Axon应用程序中处理和调度命令的过程。这里将涉及诸如聚合建模,外部命令处理程序,命令分派和测试之类的主题。
1. Aggregate 基本使用
聚合是一个常规对象,其中包含状态和更改该状态的方法。创建Aggregate对象时,您实际上是在创建“ Aggregate Root”,通常带有整个Aggregate的名称。出于此描述的目的,将使用“礼品卡”域,这使我们将GiftCard作为汇总(根)。默认情况下,Axon将您的聚合配置为“基于事件的”聚合(如此处所述)。此后,我们的基本GiftCard聚合结构将重点关注事件采购方法:
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.eventsourcing.EventSourcingHandler;
import org.axonframework.modelling.command.AggregateIdentifier;
import static org.axonframework.modelling.command.AggregateLifecycle.apply;
public class GiftCard {
@AggregateIdentifier // 1.
private String id;
@CommandHandler // 2.
public GiftCard(IssueCardCommand cmd) {
// 3.
apply(new CardIssuedEvent(cmd.getCardId(), cmd.getAmount()));
}
@EventSourcingHandler // 4.
public void on(CardIssuedEvent evt) {
id = evt.getCardId();
}
// 5.
protected GiftCard() {
}
// omitted command handlers and event sourcing handlers
}
给定的代码片段中有两个值得注意的概念,其中带有编号的Java注释,这些注释涉及以下项目符号:
@AggregateIdentifier
是GiftCard Aggregate中的外部参考点(主要用于命令,定位用)。这个字段是一个硬性要求,因为没有它,Axon将不知道给定Command的目标是哪个Aggregate。@CommandHandler
批注的构造函数,或以其他方式放置“命令处理构造函数”。此注释告诉框架给定的构造函数能够处理IssueCardCommand。- 静态
AggregateLifecycle#apply(Object ...)
是应发布事件消息时使用的对象。调用此函数后,提供的对象将在应用它们的聚合范围内以EventMessages的形式发布。 - 使用
@EventSourcingHandler
可以告诉框架,当“从其事件中获取”聚合时,应调用带注释的函数。
由于所有事件源处理程序的合并将形成聚合,因此所有状态更改都将在此处发生。
请注意,必须在聚合发布的第一个事件的@EventSourcingHandler
中设置聚合标识符。(聚合对象里必须要有一个,它用于更改聚合的状态!
)
这通常是创建事件。最后,使用特定规则解析@EventSourcingHandler
注释的函数。
这些规则与@EventHandler
带注释的方法相同。 - Axon必需的无参数构造函数。Axon Framework使用此构造函数创建一个空的聚合实例,然后使用过去的事件对其进行初始化。如果不提供此构造函数,则会在加载Aggregate时导致异常。
消息处理功能的修饰符
只要JVM的安全性设置允许Axon
Framework更改方法的可访问性,事件处理程序方法就可以是私有的。这使您可以清楚地将Aggregate的公共API与处理事件的内部逻辑分开,后者公开了生成事件的方法。
大多数IDE都可以选择忽略带有特定批注的方法的“未使用的私有方法”警告。另外,您可以在方法中添加@SuppressWarnings(“
UnusedDeclaration”)批注,以确保您不会意外删除事件处理程序方法。
2. Aggregate 中命令的处理
尽管可以将命令处理程序放置在常规组件中(如将在此处讨论的那样,但建议直接在包含处理该命令状态的聚合上定义命令处理程序。
)
要在聚合中定义命令处理程序,只需使用@CommandHandler
注释应处理命令的方法。带有@CommandHandler注释的方法将成为命令消息的命令处理程序,其中命令名称与该方法的第一个参数
的完全限定的类名称匹配。因此,以@CommandHandler注释的void handle(RedeemCardCommand cmd)
的方法签名将成为RedeemCardCommand命令消息的命令处理程序。
命令消息还可以使用不同的命令名称来分派。为了能够正确处理这些问题,可以在@CommandHandler批注中指定String commandName值。
为了使Axon知道应使用哪种Aggregate类型的实例处理命令消息,必须在命令对象中携带@Identity(@AggregateIdentifier,注解标识聚合的id,命令通过@TargetAggregateIdentifier与其匹配
)的属性使用Aggregate Identifier进行注释。 注释可以放在Command对象中的字段或访问器方法(例如getter)上。
以GiftCard聚合为例,我们可以在聚合上标识两个命令处理程序:
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.modelling.command.AggregateIdentifier;
import static org.axonframework.modelling.command.AggregateLifecycle.apply;
public class GiftCard {
@AggregateIdentifier
private String id;
private int remainingValue;
@CommandHandler
public GiftCard(IssueCardCommand cmd) {
apply(new CardIssuedEvent(cmd.getCardId(), cmd.getAmount()));
}
@CommandHandler
public void handle(RedeemCardCommand cmd) {
if (cmd.getAmount() <= 0) {
throw new IllegalArgumentException("amount <= 0");
}
if (cmd.getAmount() > remainingValue) {
throw new IllegalStateException("amount > remaining value");
}
apply(new CardRedeemedEvent(id, cmd.getTransactionId(), cmd.getAmount()));
}
// omitted event sourcing handlers
}
GiftCard处理的Command对象IssueCardCommand和RedeemCardCommand具有以下格式:
import org.axonframework.modelling.command.TargetAggregateIdentifier;
public class IssueCardCommand {
@TargetAggregateIdentifier
private final String cardId;
private final Integer amount;
public IssueCardCommand(String cardId, Integer amount) {
this.cardId = cardId;
this.amount = amount;
}
// omitted getters, equals/hashCode, toString functions
}
public class RedeemCardCommand {
@TargetAggregateIdentifier
private final String cardId;
private final String transactionId;
private final Integer amount;
public RedeemCardCommand(String cardId, String transactionId, Integer amount) {
this.cardId = cardId;
this.transactionId = transactionId;
this.amount = amount;
}
// omitted getters, equals/hashCode, toString functions
}
两个命令中都存在的cardId是对GiftCard实例的引用,因此使用@TargetAggregateIdentifier
注释进行注释。创建聚合实例的命令不需要标识目标聚合标识符,因为还不存在聚合。但是,为了保持一致性,建议在其上也注释聚合标识符。
如果您喜欢使用其他机制来路由命令,则可以通过提供自定义CommandTargetResolver来覆盖行为。此类应基于给定命令返回聚合标识符和预期版本(如果有)。
聚合创建命令处理程序
将@CommandHandler批注放置在聚合的构造函数上时,相应的命令将创建该聚合的新实例并将其添加到存储库中。这些命令不需要针对特定的聚合实例。因此,这些命令不需要任何@TargetAggregateIdentifier或@TargetAggregateVersion批注,也不会为这些命令调用自定义CommandTargetResolver。
但是,无论使用哪种命令,都强烈建议您通过Axon Server 等分发应用程序时,在给定的消息上指定一个路由密钥。这样,@TargetAggregateIdentifier会加倍,但是在缺少值得注释的字段的情况下,应添加@RoutingKey注释以确保可以路由命令。此外,可以配置不同的RoutingStrategy,如“命令分派”部分中进一步指定。
3. 业务逻辑和状态变更
在聚合中,有一个特定的位置可以执行业务逻辑验证和聚合状态更改。命令处理程序应确定聚合是否处于正确状态(先检查当前状态是否可操作,如:送货必须要等下单了才可以送吧
)。如果是,则发布一个事件。否则,根据域的需要,可能会忽略该命令或引发异常。
**在任何命令处理功能中都不应发生状态更改。事件源处理程序应该是更新聚合状态的唯一方法。**如果不这样做,则意味着从事件中获取聚合时,聚合将丢失状态更改。
综合测试装置将防止命令处理功能中的意外状态更改。因此,建议为任何汇总实施提供全面的测试案例。
何时处理事件
Aggregate 所需的
唯一状态
是决策所需的状态。因此,仅在聚合的状态
与需要的状态
验证相同时才去要处理由聚合发布的事件。
4. 从事件来源处理程序发布事件消息
在某些情况下,尤其是当汇总结构增长到不只是几个实体时,对在同一汇总的其他实体中发布的事件做出反应比较干净(此处将详细解释多实体汇总)。但是,由于在重建聚合状态时也会调用事件处理方法,因此必须采取特殊的预防措施。
可以在Event Sourcing Handler方法内apply()
新事件。这使得实体“ B”可以应用事件来响应实体“ A”做某事。在采购给定的Aggregate时重播历史事件时,Axon将忽略apply()
调用。请注意,在从事件来源处理程序发布事件消息的情况下,内部apply()
调用的事件仅在所有实体都收到第一个事件之后才发布给实体。如果需要发布更多事件,请在应用内部事件后基于实体的状态,使用apply(...).andThenApply(...)
。
对其他事件做出反应
聚合无法处理来自其自身之外其他来源的事件。这是故意的,因为事件源处理程序用于重新创建聚合的状态。为此,它只需要它自己的事件即可,因为那些事件代表着状态改变。
为了使聚合对来自其他聚合实例的事件做出反应,应该利用Sagas或事件处理组件。(分布式)
5. Aggregate 生命周期
在聚合的生命周期中,需要执行几项操作。为此,Axon中的AggregateLifecycle类提供了两个静态函数:
apply(Object) 和apply(Object, MetaData): AggregateLifecycle#apply
将在EventBus上发布事件消息,这样就知道它源自执行该操作的聚合。可以仅提供事件对象,也可以提供事件和某些特定的元数据(MetaData)。createNew(Class, Callable)
:通过处理命令实例化新的聚合。阅读此内容以获得更多详细信息。isLive()
:检查以确认聚合是否处于“活动”状态。
如果聚合完成重播历史事件以重新创建其状态,则视为“实时”。如果因此正在将Aggregate用作事件源,则AggregateLifecycle.isLive() 调用将返回false。
使用此isLive()方法,您可以执行仅应在处理新生成的事件时执行的活动。- markDeleted():将标记该函数的Aggregate实例标记为“已删除”。如果域指定可以删除/删除/关闭给定的聚合,则很有用,此后不应再允许它处理任何命令。 应该从
@EventSourcingHandler
带注释的函数中调用其函数,以确保被标记为已删除是该Aggregate状态的一部分。
来源:CSDN
作者:唯重
链接:https://blog.csdn.net/GMingZhou/article/details/104611564