problem injecting Spring bean in an Aggregate command handler using custom Aggregate repository

Using Axon framework 4.2 and axon server 4.1.1 (axonserver probably not relevant here) and using Axon spring boot starter, I am having the following issue:
My application wil not start, because Axon cannot resolve a parameter (spring bean injection) from one of the command handlers of aggregate “Klas”.

This is the aggregate: (simplified)

`
@Aggregate
public class Klas {
@AggregateIdentifier
private KlasId klasId;

private Klas() {}

Klas(KlasGegevens klasGegevens) {
apply(new KlasAangemaakt(klasId(klasGegevens),klasGegevens));
}

@CommandHandler
public AlbumId cmd(VoegAlbumToe cmd, @Qualifier(“albumIdGenerator”) AlbumIdGenerator albumIdGenerator)
throws Exception {
AlbumId albumId = albumIdGenerator.generate();
(…)
return albumId;
}

(…)
}
`

The AlbumIdGenerator is just a simple spring bean with a @Service annotation:

`
@Service(“albumIdGenerator”)
public class AlbumIdGenerator {
public AlbumIdGenerator() {}

public AlbumId createAlbumId() {
return AlbumId.generate();
}
}
`

Now, this problem arises from the fact that I define my own EventSourcingRepository for Aggregate 'Klas":
(sorry, I am mixing Java and Kotlin in my project, but that does not seem to be the problem)

`
@Configuration
open class KlasRepositoryConfig {

@Bean
open fun klasRepository(snapshotTriggerDefinition: SnapshotTriggerDefinition, eventStore: EventStore): EventSourcingRepository {
return EventSourcingRepository.builder(Klas::class.java)
.snapshotTriggerDefinition(snapshotTriggerDefinition)
.eventStore(eventStore)
.build()
}
}
`

The moment this EventSourcing klas repository (tries to be) instantiated, I get the following error:

`
java.lang.IllegalStateException: Failed to load ApplicationContext
(…)Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘klasFactory’: Unsatisfied dependency expressed through field ‘repository’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘klasRepository’ defined in class path resource [be/triptrapschool/foto/domain/klas/KlasRepositoryConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.axonframework.eventsourcing.EventSourcingRepository]: Factory method ‘klasRepository’ threw exception; nested exception is org.axonframework.messaging.annotation.UnsupportedHandlerException: Unable to resolve parameter 1 (AlbumIdGenerator) in handler public be.triptrapschool.foto.domain.album.AlbumId be.triptrapschool.foto.domain.klas.Klas.cmd(be.triptrapschool.foto.domain.klas.VoegAlbumToe,be.triptrapschool.foto.domain.album.AlbumIdGenerator) throws java.lang.Exception.

`

So Axon cannot resolve the “AlbumIdGenerator” parameter in the command handling method of Klas.
Why?
Axon tries to resolve the “AlbumIdGenerator” parameter using a number of parameter resolvers, defined in an (Axon) “MultiParameterResolverFactory”. This resolver factory contains 11 specific resolver factories. However, it needs a Parameter resolver factory which will lookup the “AlbumIdGenerator” in the spring context. This resolver factory is called the ‘SpringBeanResolverFactory’. The SpringBeanResolverFactory is missing in the list of 11 factories at the time the KlasRepository is instantiated.

The ‘SpringBeanResolverFactory’ is added later on in the startup process of Spring, by axons’s ‘AnnotationCommandHandlerBeanPostProcessor’

My question:

  1. How can I create the KlasRepository later on, AFTER the AnnotationCommandHandlerBeanPostProcessor has been executed? (so that this problem is avoided)
  2. Do you have a better suggestion to my setup, to resolve this issue?
  3. I need an instance of the klas EventSourcingRepository, because I am using a Factory to create the Klas objects. Is there another way of accessing the klas EventSourcingRepository, without defining it as a bean myself? (have it auto-created by axon at the right time)

I thank you

For completeness, I show you my “KlasFactory”, which is the reason why I need to instantiate the KlasRepository explicitely. In needs to be injected in my KlasFactory.
(in Kotlin, this one)

`
@Component
open class KlasFactory {
@Autowired
private lateinit var commands: Commands
@Autowired
private lateinit var repository: EventSourcingRepository

@CommandHandler
fun cmd(cmd: MaakKlasIndienDieNogNietBestaat): KlasId {
val klasId = klasId(cmd.klas)
try {
commands.execute(Ping(klasId))
} catch (exc: CommandExecutionException) { //might be thrown by Axon Server)
this.maakKlas(cmd.klas)
}
return klasId
}

private fun maakKlas(klasGegevens: KlasGegevens) {
try {
repository.newInstance { Klas(klasGegevens) }
} catch (exc: Exception) {
commands.execute(Ping(klasId(klasGegevens))) //Ping command handler was omitted from Klas previous post
}
}
}
`

Hi Stijn,

From a Spring wiring perspective, this seems kinda intricate to me from what your describing.
And, to be honest, I’d really have to run it myself to figure out, through debugging, how you could set the ordering correct (if it all possible).

However, I do have a couple of suggestions which might help you out.
Firstly, you’ve set the Qualifier annotation on the bean you’re trying to (parameter) resolve.
Additionally, this bean is “named” albumIdGenerator. That is however not necessarily the same as “qualified”.

Thus, this is a guess, but it might be able to resolve the bean if, instead of using @Service("albumIdGenerator"), that you’d use:

@Service

@Qualifier(“albumIdGenerator”)

If wiring proofs to be to harsh giving your usages requirement, you could introduce another way of resolving the bean than through the SpringBeanParameterResolver.

If you add an FixedValueParameterResolver, which directly resolves this generator for you, that might do the trick.

And, if event that doesn’t work, I got this final suggestion.

Does the AlbumIdGenerator really need to be a bean?

Why not make it a utility class which you can call statically?

Hope all this helps you out Stijn!

Cheers,
Steven

Hi Steven,

Separating the @Qualifier annotation had no effect.
Adding a FixedValueParameterResolver would be a solution for the issue. However, I resolved it differently:

  • I no longer instantiate my Klas Eventsourcing repository but let axon do it (Removed KlasRepositoryConfig). If I would need more controle over the configuration of the klasRepository, using it with the FixedValueParameterResolver would be fine.
  • I changed the implementation of KlasFactory (= the class where the repository was supposed to be injected into)

`
@Component
open class KlasFactory {
@Autowired
private lateinit var commands: Commands
@Autowired
private lateinit var springContext : ApplicationContext

@CommandHandler
fun cmd(cmd: MaakKlasIndienDieNogNietBestaat): KlasId {
val klasId = klasId(cmd.klas)

try {
commands.execute(Ping(klasId))
} catch (exc: CommandExecutionException) {
this.maakKlas(cmd.klas)
}

return klasId
}

private fun maakKlas(klasGegevens: KlasGegevens) {
try {
val repository = loadRepository();
repository.newInstance { Klas(klasGegevens) }
} catch (exc: Exception) {
commands.execute(Ping(klasId(klasGegevens)))
}
}

private fun loadRepository(): EventSourcingRepository {
return springContext.getBean(
“klasRepository”, EventSourcingRepository::class.java) as EventSourcingRepository
}
}
`

Now, the klasRepository is not injected anymore, but I get is from the Spring context when needed, which is much later then when Axon’s AnnotationCommandHandlerBeanPostProcessor kicked in, so now that is fine.

Why using an AlbumIdGenerator? In my little project, a Klas is responsible for creating an album if it does not exist yet. Creating an album is also creating an ID for it. Creating the id using a static method is possible (AlbumId.generate()) but as a bean, it is just easier to test:

  • you can mock the bean to set its behaviour
  • by setting its behaviour, you can test the Klas aggregate easier using aggregate fixtures. It’s just easier to specifiy/compare the events if you can predict the ID that klas will generate

With this very simple case, I could expand the static code to alter the generator behaviour in tests. But this is also works as a sample case for injecting far more complex services.

thank you!

Hi Stijn,

I am glad to hear you’ve found another way around this issue.
More importantly though, I am happy that you are sharing it with everybody here to learn from it; so thank you!

I do have one thing I’d like to point out as a reaction to the AlbumIdGenerator testing/mocking suggestions you are sharing.
Note that you can tell the Axon Test Fixture set up to ignore field for verification, like the identifier.

The Command Handling/Test Setup page shows this through the registerIgnoredField method.

A part from that, great to hear the issue has been resolved!

Cheers,
Steven

That is really cool

Note that you can tell the Axon Test Fixture set up to ignore field for verification, like the identifier.

The Command Handling/Test Setup page shows this through the registerIgnoredField method.

That is a really cool feature I did not know about. Will study the Test setup page in detail!

Thanks Steven