Hi,
We're having an issue when using an aggregate as the factory for a different aggregate. Below a simplified example of what we would like to do:
We have two separate aggregates: Analysis and Function. The analysis should has a method defineFunction which should create a Function after some validation of the Analyis' state.
@Aggregate
public class Analysis {
@AggregateIdentifier
private UUID id;
private String title;
private Analysis() {}
public Analysis(String title) {
apply(new AnalysisStartedEvent(UUID.randomUUID(), title))
}
public Function defineFunction(String description) {
return new Function(id, description);
}
@EventHandler
public void mutate(AnalysisStartedEvent event) {
id = event.getId();
title = event.getTitle();
}
}
@Aggregate
public class Function {
@AggregateIdentifier
private UUID id;
private UUID analysisId;
private String description;
private Function() {}
public Function(UUID analysisId, String description) {
apply(new FunctionDefinedEvent(analysisId, UUID.randomUUID(), description));
}
@EventHandler
public void mutate(FunctionDefinedEvent event) {
id = event.getId();
analysisId = event.getAnalyisId();
description = event.getDescription();
}
}
public class AnalysisCommandHandler {
private Repository<Analysis> analysisRepository;
private Repository<Function> functionRepository;
@Autowired
public AnalysisCommandHandler(Repository<Analysis> analysisRepository, Repository<Function> functionRepository) {
this.analysisRepository = analysisRepository;
this.functionRepository = functionRepository;
}
@CommandHandler
public void handle(DefineFunctionCommand command) {
Aggregate<Analyis> analysisAggregate = analysisRepository.load(command.getAnalyisId().toString());
functionRepository.newInstance(() -> analysisAggregate.invoke(analysis -> analysis.defineFunction(command.getDescription())));
}
}
Running the above code results in the following NullPointer exception:
java.lang.NullPointerException: null
at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936) ~[na:1.8.0_112]
at org.axonframework.common.lock.PessimisticLockFactory.lockFor(PessimisticLockFactory.java:100) ~[axon-core-3.0.2.jar:3.0.2]
at org.axonframework.common.lock.PessimisticLockFactory.obtainLock(PessimisticLockFactory.java:90) ~[axon-core-3.0.2.jar:3.0.2]
at org.axonframework.commandhandling.model.LockingRepository.doCreateNew(LockingRepository.java:104) ~[axon-core-3.0.2.jar:3.0.2]
at org.axonframework.commandhandling.model.LockingRepository.doCreateNew(LockingRepository.java:48) ~[axon-core-3.0.2.jar:3.0.2]
at org.axonframework.commandhandling.model.AbstractRepository.newInstance(AbstractRepository.java:79) ~[axon-core-3.0.2.jar:3.0.2]
Which seems to stem from the id of the Function being null. Setting the id in the Function constructor gets rid of the NullPointer but still results in the repository not containing an aggregate with said id.
I came across this discussion
https://groups.google.com/d/msg/axonframework/e2dpXxT0jeE/_-VUAVGVEgAJ which describes a similar scenario, which, judging from the discussion, does seem to work in Axon 2. Allard Buijze mentions this would be taken into account for Axon 3, so I'm wondering if there's something we've missed. I can't seem to find any documentation or examples describing this usecase.
I was able to get the scenario to work by injecting the EventBus in the domain model and publishing the event as a GenericDomainEventMessage with the FunctionId as aggregateidentifier and a sequence of 0. However, we would like to keep all this infrastructure code out of the domain models.
I hope I've described our use case thoroughly enough and that someone can provide a solution. If more information is needed, please let me know and I'll try to provide them.
Thanks in advance,
Jan-Willem