Axon5: Spring bean initialization order issue

Hi,

This is a minimal reproducer for a Spring bean initialization issue I hit while migrating from Axon Framework 4 (AF4) to Axon Framework 5 (AF5).

When using a Spring bean that has a QueryGateway injected like this:

@Service
public class SettingsService {
  private final QueryGateway queryGateway;

  public SettingsService(QueryGateway queryGateway) {
    this.queryGateway = queryGateway;
  }
  // some code
}

If we use this bean somewhere else, e.g., in the @Configuration class of a projection:

@Configuration
public class ProjectionConfiguration {

  public static String PROCESSOR_NAME = "courses-processor";


  @Bean
  EventProcessorDefinition coursesProcessor(SettingsService settingsService) {
    return EventProcessorDefinition
        .pooledStreaming(PROCESSOR_NAME)
        .assigningHandlers(descriptor -> descriptor.beanType().getPackageName()
            .endsWith("coursestats.projection"))
        .customized(config -> config
            .initialSegmentCount(4)
            .batchSize(10)
        );
  }
}

and there is an event handling method in that package:

@Component
class SimpleProjector {

  @EventHandler
  void handle(Object event, QueryUpdateEmitter emitter) {
  // empty for demo purposes
  }
}

we get a startup error:

Parameter 0 of constructor in org.axonframework.examples.university.projection.SettingsService required a bean of type 'org.axonframework.messaging.queryhandling.gateway.QueryGateway' that could not be found.

In our existing AF4 apps this pattern worked fine.

Suspected cause: it seems that Axon creates the EventProcessorDefinition beans very early, and at that point the QueryGateway is not yet available for constructor injection in SettingsService.

Question: do we really need to rework the Spring bean dependencies across all our apps, or is there a recommended way to handle this in AF5?

Klaus

How are you planning to use the SettingsService inside the EventProcessorDefinition?

The EventProcessorDefinition beans are indeed initialized very early. At that time, the infrastructure components are still being constructed. Spring Boot 4 is very strict when it comes to early initialization/circular dependencies.

Instead of injecting the bean, you can (probably) also use the config parameter in the customized method to access your beans: config.getComponent(SettingsService.class).

Hello Allard,

my example with the SettingsService in the EventProcessorDefinition was just an example to reproduce the error in a minimal way. In reality, we had a more complex setup.

We had previously defined the ProcessingGroup configuration (segments, batch size, DLQ) via Spring beans directly in our ProjectionHandler classes. This worked well with AF4.

I have now learned that this doesn’t work the same way with AF5, since EventProcessorDefinitions are initialized very early. Also, @Configuration classes that require e.g. QueryGateway are often a problem, since @Configurations are initialized early and QueryGateway rather late.

In the meantime I have found workarounds so that I didn’t have to completely restructure the Spring bean initialization order:

  • ProjectionHandler classes: use @Namespace and @EventHandler methods, no @Bean definitions
  • One @Configuration class per namespace with EventProcessorDefinitions
  • Spring beans in @Configuration classes that inject e.g. QueryGateway: use @Lazy (org.springframework.context.annotation.Lazy)

For example:

@Configuration
class UserManagementConfiguration {
 ...
  @Bean
  fun externalUserService(
    // @Lazy: avoid pulling QueryGateway into early Axon bootstrap
    @Lazy settingsQueryService: SettingsQueryService,
    @Lazy queryGateway: QueryGateway,
    ...
  ): ExternalUserService {
  ...
  }
}

I have now worked around the bean initialization problem, and forgot to post the “solution” here.
Perhaps you should try to mention this topic somewhere in the migration guide - it takes a while to understand the problem in the first place.

Klaus