Hello,
I am currently working on a domain model, where the ubiquitous language basically calls for different kinds of the same upper class. It is a domain I know well for years now. Not only do these different types share a set of basic commands, but they are also referenced by other aggregates in the same generic way.
The problem I run into:
-
Test is superclass
-
Test1/Test2 Subclasses
-
Constructor commands work fine
-
The commands declared by Test work fine
-
The commands declared to be handled by Test1/Test2 fail in command handling
The stacktrace below is created when creating a Test1 and dispatching a ModifyTest1Command.
My current very ugly workaround: Make Test non-abstract and an @Aggregate. Declare all CommandHandlers for all sub-types in Test, let them throw an Exception when called there. @Override the appropriate command handler in Type1/Type2 to add domain logic. So I ened up with a very long list of these dummy command and event handlers in my top level class. Makes maintenance a mess as well.
Any idea on how to resolve this cleanly that the example below works ? Or am I doing something wrong?
Thanks everybody.
Dominic
I made up a small toy example of this (using lombok and kotlin):
API in .kt:
`
class CreateTest1Command(val testId: String)
class CreateTest2Command(val testId: String)
class ModifyTestCommand(@TargetAggregateIdentifier val testId: String)
class ModifyTest1Command(@TargetAggregateIdentifier val testId: String)
class ModifyTest2Command(@TargetAggregateIdentifier val testId: String)
class Test1CreatedEvent(@TargetAggregateIdentifier val testId: String)
class Test2CreatedEvent(@TargetAggregateIdentifier val testId: String)
`
`
@Slf4j
public abstract class Test {
@Getter
@Setter
@AggregateIdentifier
protected String testId;
@CommandHandler
public void handle(ModifyTestCommand command) {
log.info(“handle by {}: {}”, this.getClass().getSimpleName(), command);
}
}
`
`
@Slf4j
@NoArgsConstructor
@Aggregate(repository = “testRepository”)
public class Test1 extends Test {
@CommandHandler
public Test1(CreateTest1Command command) {
log.info(“1-handle: {}”, command);
apply(new Test1CreatedEvent(command.getTestId()));
}
@CommandHandler
public void handle(ModifyTest1Command command) {
log.info(“1-handle: {}”, command);
}
@EventSourcingHandler
public void on(Test1CreatedEvent event) {
setTestId(event.getTestId());
}
}
`
`
@Slf4j
@NoArgsConstructor
@Aggregate(repository = “testRepository”)
public class Test2 extends Test {
@CommandHandler
public Test2(CreateTest2Command command) {
log.info(“2-handle: {}”, command);
apply(new Test2CreatedEvent(command.getTestId()));
}
@CommandHandler
public void handle(ModifyTest2Command command) {
log.info(“2-handle: {}”, command);
}
@EventSourcingHandler
public void on(Test2CreatedEvent event) {
setTestId(event.getTestId());
}
}
`
`
@Configuration
public class TestConfiguration {
@Autowired
EventStore eventStore;
@Bean
public Repository testRepository() {
return new EventSourcingRepository<>(new TestAggregateFactory(), eventStore);
}
}
@Slf4j
public class TestAggregateFactory implements AggregateFactory {
@Override
public Test createAggregateRoot(String aggregateIdentifier, DomainEventMessage<?> firstEvent) {
if (firstEvent.getPayloadType().equals(Test1CreatedEvent.class)) {
log.info(“Instantiate -> Test1”);
return new Test1();
} else if (firstEvent.getPayloadType().equals(Test2CreatedEvent.class)) {
log.info(“Instantiate -> Test2”);
return new Test2();
} else {
throw new IllegalArgumentException(
"this aggregate factory does not support " + firstEvent.getPayloadType().getSimpleName());
}
}
@Override
public Class getAggregateType() {
return Test.class;
}
}
`
`
Command resulted in exception: org.openconjurer.directory.command.agent.ModifyTest1Command
java.lang.NullPointerException: null
at org.axonframework.commandhandling.model.inspection.AnnotatedAggregate.lambda$handle$3(AnnotatedAggregate.java:194) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.model.AggregateLifecycle.executeWithResult(AggregateLifecycle.java:75) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.model.inspection.AnnotatedAggregate.handle(AnnotatedAggregate.java:192) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.model.LockAwareAggregate.handle(LockAwareAggregate.java:60) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.AggregateAnnotationCommandHandler$AggregateCommandHandler.handle(AggregateAnnotationCommandHandler.java:192) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.AggregateAnnotationCommandHandler$AggregateCommandHandler.handle(AggregateAnnotationCommandHandler.java:186) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.AggregateAnnotationCommandHandler.handle(AggregateAnnotationCommandHandler.java:148) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.AggregateAnnotationCommandHandler.handle(AggregateAnnotationCommandHandler.java:40) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:57) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.messaging.interceptors.CorrelationDataInterceptor.handle(CorrelationDataInterceptor.java:46) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:55) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.messaging.unitofwork.DefaultUnitOfWork.executeWithResult(DefaultUnitOfWork.java:66) ~[axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:143) [axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:119) [axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.SimpleCommandBus.dispatch(SimpleCommandBus.java:89) [axon-core-3.0-M5.jar:3.0-M5]
at org.axonframework.commandhandling.CommandBus.dispatch(CommandBus.java:45) [axon-core-3.0-M5.jar:3.0-M5]
`