建議在包含處理狀態(tài)命令的聚合中直接定義命令處理器仓洼,因為命令處理器有可能需要該集合的狀態(tài)來執(zhí)行其任務(wù)。
要在一個聚合上定義一個命令處理器,只需用@CommandHandler注解命令處理方法即可堤舒。帶@CommandHandler注解方法的規(guī)則和其他處理方法都是一樣的色建。然而,命令不僅通過他們的有效載荷(payload)進行路由。命令消息攜帶一個名字,該名稱默認為命令對象的完全限定類名舌缤。
默認情況下,帶@CommandHandler注解的方法允許以下參數(shù)類型:
- 第一個參數(shù)是命令消息的有效載荷箕戳。它的類型也可能是Message或CommandMessage,如果@CommandHandler 注解明確定義命令處理器的名稱陵吸。默認情況下玻墅,命令名是命令的有效載荷的完全限定類名壮虫。
- 用@MetaDataValue注解的參數(shù),將用注解上的鍵對元數(shù)據(jù)值進行解析囚似。如果需要為false(默認值),則在元數(shù)據(jù)值不存在時傳遞NULL饶唤。如果需要為True徐伐,在元數(shù)據(jù)值不存在時办素,該解析器將不匹配并阻止該方法被調(diào)用。
- 參數(shù)的類型元數(shù)據(jù)將注入整個CommandMessage的元數(shù)據(jù)祸穷。
- UnitOfWork類型的參數(shù)獲取當(dāng)前工作單元注入。這允許命令處理器注冊的行為在工作單元的特定階段執(zhí)行粱哼,或獲得與它注冊的資源的訪問。
- Message或CommandMessage類型的參數(shù)揭措,將得到完整的消息胯舷,包括有效載荷和元數(shù)據(jù)。如果方法需要多個元數(shù)據(jù)字段或包裝消息的其他屬性绊含,則此方法非常有用桑嘶。
為了使Axon知道哪一個聚合類型的實例應(yīng)該處理命令消息,命令對象的屬性傳送聚合標(biāo)識符躬充,必須用@TargetAggregateIdentifier注解逃顶。注解可以放置在任何字段或訪問器方法上(例如getter)。
創(chuàng)建聚合實例的命令不需要標(biāo)識目標(biāo)聚合標(biāo)識符充甚,雖然建議標(biāo)注聚合標(biāo)識符以政。
如果你喜歡使用另一個機制路由命令,這種行為可以通過提供一個自定義CommandTargetResolver來重寫伴找。這個類應(yīng)該返回聚合標(biāo)識符和預(yù)期的版本(如果有的話)基于給定的命令盈蛮。
注意
當(dāng)@CommandHandler注解放在一個聚合的構(gòu)造函數(shù)上時,相應(yīng)的命令將創(chuàng)建一個新的聚合實例技矮,并將它添加到存儲庫抖誉。這些命令不需要針對特的定聚合實例殊轴。因此,這些命令不需要任何@TargetAggregateIdentifier或@TargetAggregateVersion注解袒炉,也不會調(diào)用自定義CommandTargetResolver旁理。
當(dāng)一個命令創(chuàng)建一個聚合實例時,該命令的回調(diào)函數(shù)在命令執(zhí)行成功執(zhí)行后我磁,將得到聚合標(biāo)識符韧拒。
public class MyAggregate {
@AggregateIdentifier
private String id;
@CommandHandler
public MyAggregate(CreateMyAggregateCommand command) {
apply(new MyAggregateCreatedEvent(IdentifierFactory.getInstance().generateIden
tifier()));
}
// no-arg constructor for Axon
MyAggregate() {
}
@CommandHandler
public void doSomething(DoSomethingCommand command) {
// do something...
}
// code omitted for brevity. The event handler for MyAggregateCreatedEvent must set the id field
}
public class DoSomethingCommand {
@TargetAggregateIdentifier
private String aggregateId;
// code omitted for brevity
}
Axon的配置API可用于配置聚合。例如:
Configurer configurer = ...
// to use defaults:
configurer.configureAggreate(MyAggregate.class);
// allowing customizations:
configurer.configureAggregate(
AggregateConfigurer.defaultConfiguration(MyAggregate.class)
.configureCommandTargetResolver(c -> new CustomCommandTargetResolver()));
@CommandHandler注釋并不局限于聚合根十性。把所有命令處理器放在根里叛溢,有時會導(dǎo)致聚合根中存在大量的方法,而它們中的許多只簡單地調(diào)用轉(zhuǎn)發(fā)給底層實體之一。如果是這樣,你可以把@CommandHandler注解在一個底層的實體的方法上劲适。Axon找到這些帶注釋的方法,聚合根中聲明的實體字段必須用@AggregateMember標(biāo)明楷掉。注意,命令處理器只檢查帶注解的字段的聲明類型。如果一個字段值為空時傳入命令到實體,就會拋出一個異常霞势。
public class MyAggregate {
@AggregateIdentifier
private String id;
@AggregateMember
private MyEntity entity;
@CommandHandler
public MyAggregate(CreateMyAggregateCommand command) {
apply(new MyAggregateCreatedEvent(...);
}
// no-arg constructor for Axon
MyAggregate() {
}
@CommandHandler
public void doSomething(DoSomethingCommand command) {
// do something...
}
// code omitted for brevity. The event handler for MyAggregateCreatedEvent must set the id field
// and somewhere in the lifecycle, a value for "entity" must be assigned to be able to accept
// DoSomethingInEntityCommand commands.
}
public class MyEntity {
@CommandHandler
public void handleSomeCommand(DoSomethingInEntityCommand command) {
// do something
}
}
請注意烹植,在聚合中每個命令必須只對應(yīng)一個處理器。這意味著你不能用@CommandHandler標(biāo)注多個實體(either root nor not愕贡,包含是根和不是根的所有實體)來處理相同的命令類型草雕。如果你需要有條件地路由命令到一個實體,這些實體的父類應(yīng)該處理命令,并根據(jù)apply的條件轉(zhuǎn)發(fā)該命令。
字段的運行時類型不需要精確地聲明類型固以。然而墩虹,@CommandHandle方法只檢查被@AggregateMember標(biāo)記的字段的聲明類型。
也可以用@AggregateMember去注釋包含實體的集合和Map憨琳。在后一種情況下诫钓,map的值有望包含實體,而鍵包含一個用作它們引用的值篙螟。
作為一個命令需要被路由到正確的實例菌湃,這些實例必須被正確地標(biāo)識惧所。它們的“ID”字段必須用@ EntityId標(biāo)記。命令的屬性將用于查找該消息應(yīng)被路由到的實體下愈,默認為被標(biāo)識的字段的名稱寞忿。例如顶岸,當(dāng)標(biāo)記一個名為“myentityid”字段叫编,命令必須具有相同名稱的屬性搓逾。這意味著必須提供個getmyentityid或myentityid()方法杯拐。如果字段的名稱和路由屬性不同,你可以提供一個值顯式使用 @EntityId(routingKey = "customRoutingProperty")朗兵。
如果在帶注解的集合和Map中沒有實體能被找到余掖,Axon會拋出一個IllegalStateException異常礁鲁。顯然,聚合不能夠在那個時間點上處理命令。
注意
字段聲明的集合或Map應(yīng)該包含適當(dāng)?shù)姆盒?允許Axon識別實體的類型包含在集合或Map中冗美。如果不可能添加泛型在聲明中(例如因為你已經(jīng)使用了一個自定義泛型類型的實現(xiàn)),你必須指定實體的類型析二,用于entityType屬性@AggregateMember注解。
外部命令處理器
在某些情況下,想要直接向一個聚合實例路由命令是不可能漆改。在這種情況下,可以注冊一個命令處理器對象准谚。命令處理器對象是一個簡單的(常規(guī)的)對象,是帶@CommandHandle注解的方法樊破。與集合的情況不同唆铐,命令處理器對象只有單個實例,該對象處理其方法中聲明的所有命令類型顺少。
public class MyAnnotatedHandler {
@CommandHandler
public void handleSomeCommand(SomeCommand command, @MetaDataValue("userId") String
userId) {
// whatever logic here
}
@CommandHandler(commandName = "myCustomCommand")
public void handleCustomCommand(SomeCommand command) {
// handling logic here
}
}
// To register the annotated handlers to the command bus:
Configurer configurer = ...
configurer.registerCommandHandler(c -> new MyAnnotatedHandler());