Hi there
Both Steven and Milan wrote interesting articles about using a custom ParameterResolverFactory.
- Parameter Resolvers in Axon
- code-samples/EmailAlreadyExistsResolverFactory.java at master · AxonIQ/code-samples · GitHub
So I followed the instructions to write my own ParameterResolverFactory. And it actually works and kicks in – but only for @CommandHandler annotated methods. But not for methods annotated with @EventSourcingHandler. Using a test fixture I receive this:
Caused by: org.axonframework.test.FixtureExecutionException: No resource of type [be.acerta.ce.calendar.timespend.backend.domain.schedule.ConnectSchedule] has been registered. It is required for one of the handlers being executed.
at org.axonframework.test.FixtureResourceParameterResolverFactory$FailingParameterResolver.resolveParameterValue(FixtureResourceParameterResolverFactory.java:58)
at org.axonframework.messaging.annotation.AnnotatedMessageHandlingMember.resolveParameterValues(AnnotatedMessageHandlingMember.java:168)
at org.axonframework.messaging.annotation.AnnotatedMessageHandlingMember.handle(AnnotatedMessageHandlingMember.java:144)
at org.axonframework.messaging.annotation.AnnotatedHandlerInspector$NoMoreInterceptors.handle(AnnotatedHandlerInspector.java:372)
at org.axonframework.modelling.command.inspection.AnnotatedAggregateMetaModelFactory$AnnotatedAggregateModel.lambda$doPublish$17(AnnotatedAggregateMetaModelFactory.java:565)
... 106 common frames omitted
Relevant code is:
In the Configuration:
...
@Bean
public Repository<TimeSpendLapse> timeSpendLapseRepository(EventStore eventStore, ConnectScheduleResolverFactory parameterResolverFactory) {
return EventSourcingRepository.builder(TimeSpendLapse.class).eventStore(eventStore).parameterResolverFactory(parameterResolverFactory).build();
}
...
In the test:
class TimeSpendLapseTest {
private FixtureConfiguration<TimeSpendLapse> fixture;
private final ConnectScheduleRepository connectScheduleRepository = mock(ConnectScheduleRepository.class);
private final ConnectScheduleResolverFactory connectScheduleResolverFactory = new ConnectScheduleResolverFactory(connectScheduleRepository);
@BeforeEach
void setup() {
fixture = new AggregateTestFixture<>(TimeSpendLapse.class);
fixture.registerInjectableResource(fixture.getRepository());
fixture.registerInjectableResource(connectScheduleResolverFactory);
fixture.registerParameterResolverFactory(connectScheduleResolverFactory);
fixture.registerAnnotatedCommandHandler(new AssignConnectScheduleCommandHandler());
}
@Test
void shouldAssignConnectSchedule_ConnectScheduleExistsInNewWorld() {
ConnectScheduleId connectScheduleId = ConnectScheduleId.from(UUID.randomUUID().toString());
when(this.connectScheduleRepository.findBy(connectScheduleId)).thenReturn(Optional.of(CONNECT_SCHEDULE));
AssignConnectScheduleCommand command = new AssignConnectScheduleCommand(TimeSpendLapseId.fromConnectAgreement("123"), AgreementId.from(CONNECT_AGREEMENT_ID1), EmployerId.from(CONNECT_EMPLOYER_ID1), EmployeeId.from(CONNECT_EMPLOYEE_ID1), LocalDate.now(), connectScheduleId);
fixture.givenNoPriorActivity()
.when(command)
.expectSuccessfulHandlerExecution()
.expectEvents(new ConnectScheduleAssignedEvent(command.getTimeSpendLapseId(), command.getConnectScheduleId(), ExternalEntityReference.fromConnectAgreement(command.getConnectAgreementId().getValue()), command.getConnectEmployerId(), command.getConnectEmployeeId(), new ScheduleOverruling(Lists.newArrayList())));
}
...
The (working) external commandhandler:
@Component
@Slf4j
public class AssignConnectScheduleCommandHandler {
@CommandHandler
public void handle(AssignConnectScheduleCommand command,
@Autowired Repository<TimeSpendLapse> timeSpendLapseRepository,
@Autowired DeadlineManager deadlineManager,
ConnectSchedule connectSchedule) {
....
}
→ ConnectSchedule-parameter is nicely resolved and injected.
The problematic eventsourcinghandler:
@Aggregate(
repository = "timeSpendLapseRepository"
)
@Entity
@SuppressWarnings({"UnusedDeclaration", "FieldCanBeLocal"})
public class TimeSpendLapse {
@AggregateIdentifier
TimeSpendLapseId timeSpendLapseId;
@AggregateMember
List<TimeSpend> allTimeSpends;
List<ConnectSchedule> allSchedules = Lists.newArrayList();
TimeSpendLapse() {
}
...
@EventSourcingHandler
public void on(ConnectScheduleAssignedEvent event, ConnectSchedule connectSchedule) {
this.timeSpendLapseId = event.getTimeSpendLapseId();
this.allSchedules.add(connectSchedule);
}
and the actual ParameterResolverFactory:
@Component
public class ConnectScheduleResolverFactory implements ParameterResolver<ConnectSchedule>, ParameterResolverFactory {
ConnectScheduleRepository connectScheduleRepository;
@Autowired
public ConnectScheduleResolverFactory(ConnectScheduleRepository connectScheduleRepository) {
this.connectScheduleRepository = connectScheduleRepository;
}
@Override
public ParameterResolver createInstance(Executable executable, Parameter[] parameters, int parameterIndex) {
if (ConnectSchedule.class.equals(parameters[parameterIndex].getType())) {
return this;
}
return null;
}
@Override
public ConnectSchedule resolveParameterValue(Message<?> message) {
if (matches(message)) {
ConnectScheduleId connectScheduleId = ((RefersConnectSchedule) message.getPayload()).getConnectScheduleId();
return connectScheduleRepository.findBy(connectScheduleId).orElse(null);
}
throw new IllegalArgumentException("Message payload not of type RefersConnectSchedule");
}
@Override
public boolean matches(Message<?> message) {
return RefersConnectSchedule.class.isAssignableFrom(message.getPayloadType());
}
@Override
public Class<?> supportedPayloadType() {
return RefersConnectSchedule.class;
}
}
While debugging, we can see these ParameterResolverFactory-s for commandhandler:
But for eventsourcinghandler we see this:
So I don’t see my custom ConnectScheduleParameterResolverFactory. Which is the problem of course – but what am I missing here?? I’ve registered it in the fixture.
Moreover, when we run the actual application (<> fixture), we see that there is NO problem, i.e. the custom parameter resolver kicks in !
Smells like a bug (in the AggregateFixture-stuff) to me.
I’m using axon-bom version 4.5.11 i.e. the latest at this moment.
Thanks in advance for your help
Christian