I have an aggregate with multiple AggregateMembers. No is the problem that the for 1 aggregateMember (Referentschap) the CommandHandlers in this aggregate member are not found. This problem also occurs with junit testing using the AggregateTestFixture<>(Onderzoek.class).
In one AggregateMember Waarschuwing
the CommandHandlers are reached as it should, but in the other aggregateMember Referentschap
the Commandhandlers are not found.
My question is how can i work around this problem?
Is there a way that i can make a configuration to fix this?
Curious issue is that in a copy of the project for some debugging, the same issue arises but then the problem is swapped to Waarschuwing
and is Referentschap
ok.
This curious swap of problems gives me the impression that there is an issue in how the CommandHandlers are collected.
update:
On a hint of Axoniq is placed only the @Aggregae
annotation on the root aggregate. Cleared chaces and all from my Intellij IDE and rerun the junit test.
Now the not found ComandHandler issue rises on both the AggregateMembers Referentschap
and Waarschuwing
.
I have made some integration test with an Axon testcontainer and they al succeed. As it seems is it a JUnit test problem, where the order of extending of aggregate classes is relevant for which CommandHandler is found.
Axon 4.5.17
Spring boot 2.7.12
Java temurin 17.0.6
@NoArgsConstructor
public class Onderzoek extends OnderzoekReferentschap {
@CommandHandler
public Onderzoek(InitieerOnderzoek command) {
apply(OnderzoekGeinitieerd.builder().build());
}
@EventSourcingHandler
public void onderzoekGeinitieerd(OnderzoekGeinitieerd event) {
this.onderzoekId = event.getOnderzoekId();
this.onderzoekStatus = INITIEEL;
}
@NoArgsConstructor
public abstract class OnderzoekReferentschap extends OnderzoekMaatregel {
@AggregateMember
private final Map<UUID, Referentschap> referentschappen = new HashMap<>();
@CommandHandler
public void verwerkReferentschapOntstaan(VerwerkReferentschapOntstaan command) {
apply(ReferentschapOntstaanVerwerkt.builder()
.onderzoekId(command.getOnderzoekId())
.referentschapId(command.getReferentschapId())
.build());
}
}
@EventSourcingHandler
public void referentschapOntstaanVerwerkt(ReferentschapOntstaanVerwerkt event) {
UUID referentschapId = event.getReferentschapId();
Referentschap referentschap = Referentschap.builder()
.referentschapId(referentschapId)
.persoonId(event.getPersoonId())
.build();
referentschappen.put(referentschapId, referentschap);
}
}
@NoArgsConstructor
abstract class OnderzoekMaatregel extends OnderzoekBasis {
@AggregateMember
final Map<UUID, Waarschuwing> waarschuwingen = new HashMap<>();
@CommandHandler
public void verwerkWaarschuwingOpgelegd(VerwerkWaarschuwingOpgelegd command) {
apply(WaarschuwingOpgelegdVerwerkt.builder()
.onderzoekId(command.getOnderzoekId())
.referentschapId(command.getWaarschuwingId())
.build());
}
@EventSourcingHandler
public void waarschuwingOpgelegdVerwerkt(WaarschuwingOpgelegdVerwerkt event) {
UUID waarschuwingId = event.getWaarschuwingId();
waarschuwingen.put(waarschuwingId, new Waarschuwing(waarschuwingId));
}
}
@NoArgsConstructor
abstract class OnderzoekBasis {
@AggregateIdentifier
UUID onderzoekId;
OnderzoekStatus onderzoekStatus;
}
The following class are the AggregateMembers with the troublesome Not Found CommandHandlers.
@Aggregate
@EqualsAndHashCode
@NoArgsConstructor
public class Referentschap {
@EntityId
private UUID referentschapId;
// This command handler is never reached.
@CommandHandler
public void verwerkReferentschapBijgewerkt(VerwerkReferentschapBijgewerkt command) {
apply(ReferentschapBijgewerktVerwerkt.builder()
.onderzoekId(command.getOnderzoekId())
.referentschapId(command.getReferentschapId())
.einddatum(command.getEinddatum())
.build());
}
@Aggregate
@EqualsAndHashCode
@NoArgsConstructor
public class Waarschuwing {
@EntityId
private UUID waarschuwingId;
// this command handler is never reached.
@CommandHandler
public void verwerkWaarschuwingVerlopen(VerwerkWaarschuwingVerlopen command) {
apply(WaarschuwingVerlopenVerwerkt.builder()
.onderzoekId(command.getOnderzoekId())
.waarschuwingId(command.getWaarschuwingId())
.verloopdatum(command.getVerloopdatum())
.build());
}
}
It's a hunch, but I guess Axon Framework's AggregateModel
isn't correctly constructed due to the @Aggregate
annotation on your Aggregate Members too.
Aggregate Members should only be referenced within the aggregate through the @AggregateMember
annotation. The @Aggregate
annotation is there to:
If you have a "simple" aggregate, you only need to use the @Aggregate
annotation once, on the so-called Aggregate Root.
If you have a polymorphic aggregate, you only have to use the @Aggregate
annotation on the root class and all concrete implementations (essentially the children of the graph).
Any classes that are not a root aggregate or polymorphic aggregate do not need the @Aggregate
annotation.
My hunch that this is the culprit stems from the fact the @Aggregate
annotation performs the auto-wiring and is used for polymorphic aggregate logic, by the way.
So, please remove the annotation from your members and tell us whether that solves the problem!
After receiving the sample project through a different channel, I was able to debug the scenario @Jan was facing. The Aggregate structure turned out to contain two inconsistencies we had to deal with.
One - Polymorphic Aggregate Members
First and foremost, the sample project contains an aggregate with polymorphic entities (read: @AggregateMember
annotated fields inside the aggregate).
Currently, Axon Framework does not investigate the entire hierarchy of the Type
of the field that's annotated to be an entity.
Due to this, Axon Framework will only discover message-handling methods on the actual annotated field, not on any other layer in the hierarchy.
To clarify, regard the following sample:
public class AggregateRoot {
@AggregateMember
private RootEntity entity
// omitting message handlers for simplicity
}
public RootEntity {
// omitting message handlers for simplicity
}
public ChildEntity {
// Message handlers here are not found!
}
The AggregateRoot
aggregate class has an entity field of type RootEntity
. By annotating it with @AggregateMember
, you tell Axon Framework, "this class has message handlers you need to register with the message buses." However, this support does not extend to the implementation of the RootEntity
.
Two - Aggregate Members on each level in the Aggregate Hierarchy
The second predicament had to do with the fact Entities/Aggregate Members were present on several layers in the Aggregate hierarchy. Axon Framework would only move one layer down the (so-called) "child entity set" to uncover command handlers to register with the internal Aggregate Model.
However, it is perfectly valid to have Aggregate Members on several layers of the Aggregate hierarchy. Due to this bug, the Framework would thus miss aggregate members during the construction of the internal model.
As this was a bug, a pull request was provided to fix this, which you can find here. This fix is part of Axon Framework release 4.8.1, by the way.