Custom ParameterResolverFactory not kicking in for @EventSourcingHandler-annotated methods (while it works for @CommandHandler annotated methods)

Hi there

Both Steven and Milan wrote interesting articles about using a custom ParameterResolverFactory.

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

Hi Christian, is there any change the command implements RefersConnectSchedule and the event does not?
The resolver you have written has a condition that the payload of the message (be it command or event) is of that type. There is no difference in the framework for resolving injectable resources for different types of messages, it should work the same.

If this is not the case, can you share the command, event and interface with me? Then I can try to reproduce it!

I’ve been looking again. The following call will force the repository to be created and initialized:

        fixture.registerInjectableResource(fixture.getRepository());

This will only be done once per test. Since the connectScheduleResolverFactory is registered after, it is rendered useless. I think removing the call, or moving it to the end of the setup() method will fix your problem!

Edit: We created an enhancement issue for this on the framework: AggregateTestFixture creates EventSourcingRepository and does not invalidate it · Issue #2129 · AxonFramework/AxonFramework · GitHub

Hi Mitchell,

No they both implement RefersConnectSchedule.

@Value
public class AssignConnectScheduleCommand implements RefersConnectSchedule {

    @TargetTypedAggregateIdentifier(TimeSpendLapseId.AGGREGATE_TYPE)
    TimeSpendLapseId timeSpendLapseId;

    AgreementId connectAgreementId;
    EmployerId connectEmployerId;
    EmployeeId connectEmployeeId;
    
    LocalDate assignmentDate;

    // new world id
    ConnectScheduleId connectScheduleId;

}
...
@Value
@DomainEvent
@EventSourcedDomainEvent(name = "ConnectScheduleAssignedEvent")
public class ConnectScheduleAssignedEvent implements TimeSpendEvent, RefersConnectSchedule {

    TimeSpendLapseId timeSpendLapseId;
    ConnectScheduleId connectScheduleId;
    ExternalEntityReference externalEntityReference;
    EmployerId employerId;
    EmployeeId employeeId;
    ScheduleOverruling scheduleOverruling;

}
...
public interface RefersConnectSchedule {

    ConnectScheduleId getConnectScheduleId();
}

Hi again

Removing yields this:

org.axonframework.test.FixtureExecutionException: No resource of type [org.axonframework.modelling.command.Repository] 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.WrappedMessageHandlingMember.handle(WrappedMessageHandlingMember.java:63)
	at org.axonframework.commandhandling.AnnotationCommandHandlerAdapter.handle(AnnotationCommandHandlerAdapter.java:119)
	at org.axonframework.commandhandling.AnnotationCommandHandlerAdapter.handle(AnnotationCommandHandlerAdapter.java:45)
	at org.axonframework.messaging.DefaultInterceptorChain.proceed(DefaultInterceptorChain.java:57)
	at org.axonframework.messaging.unitofwork.DefaultUnitOfWork.executeWithResult(DefaultUnitOfWork.java:74)
	at org.axonframework.commandhandling.SimpleCommandBus.handle(SimpleCommandBus.java:177)
	at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:143)
	at org.axonframework.commandhandling.SimpleCommandBus.dispatch(SimpleCommandBus.java:111)
	at org.axonframework.test.aggregate.AggregateTestFixture.lambda$when$3(AggregateTestFixture.java:451)
	at org.axonframework.test.aggregate.AggregateTestFixture.executeAtSimulatedTime(AggregateTestFixture.java:402)
	at org.axonframework.test.aggregate.AggregateTestFixture.when(AggregateTestFixture.java:451)
	at org.axonframework.test.aggregate.AggregateTestFixture.when(AggregateTestFixture.java:438)
	at be.acerta.ce.calendar.timespend.backend.domain.timespend.TimeSpendLapseTest.shouldAssignConnectSchedule_ConnectScheduleExistsInNewWorld(TimeSpendLapseTest.java:92)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

But this works:

    @BeforeEach
    void setup() {
        fixture = new AggregateTestFixture<>(TimeSpendLapse.class);
        fixture.registerInjectableResource(connectScheduleResolverFactory);
        fixture.registerParameterResolverFactory(connectScheduleResolverFactory);
        fixture.registerInjectableResource(fixture.getRepository());
        fixture.registerAnnotatedCommandHandler(new AssignConnectScheduleCommandHandler());
    }

So, registerInjectableResource() should come AFTER registerParameterResolverFactory().

Thanks for your help !

1 Like