Repositories with SpringAxonAutoConfigurer

Hi!

I’m using Axon 3.07 with SpringAxonAutoConfigurer and I’m having troubles setting up custom Repositories.

Whenever I manually configure a Repository bean, I get an exception that my additional command handler parameters cannot be injected in the Aggregate.

Everything works fine without manually specifying a repository though. The reason I need a custom repository bean is to configure Snapshotting.

By the way, it seems that in my case SpringAxonAutoConfigurer registers beans with their full-qualified name instead of their simple name. Hence, if I don’t manually specify the repository name in the Aggregate annotation, it looks for a repository with the Name “com.my.sample.calendar.CalendarRepository” instead of “calendarRepository”.

This is the exception I receive:

`
WARN 60464 — [ main] ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘calendarRepository’ defined in class path resource [com/my/sample/configuration/AxonConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.axonframework.commandhandling.model.Repository]: Factory method ‘calendarRepository’ threw exception; nested exception is org.axonframework.messaging.annotation.UnsupportedHandlerException: Unable to resolver parameter 2 (CalendarDao) in handler public com.my.sample.calendar.Calendar(com.my.sample.calendar.command.CreateCalendar,org.axonframework.messaging.MetaData,com.my.sample.calendar.CalendarDao).

`

Here is my Aggregate:

`

@Aggregate(repository = “calendarRepository”)
public class Calendar {

@AggregateIdentifier
private UUID id;
private String name;
private List days;
private Integer calendarIdentifier;

// no arg constructor for axon
public Calendar() {
}

@CommandHandler
public Calendar(CreateCalendar cmd, MetaData metadata, CalendarDao dao) {
Integer calendarIdentifier = dao.claimCalendarId(cmd.getId()).orElseThrow(CalendarLimitReachedException::new);
dao.claimCalendarName(cmd.getId(), cmd.getName());
apply(CalendarCreated.builder()
.id(cmd.getId())
.calendarIdentifier(calendarIdentifier)
.name(cmd.getName())
.days(cmd.days())
.build(), metadata);
}

@EventSourcingHandler
public void imposeCreated(CalendarCreated evt) {
this.id = evt.getId();
this.calendarIdentifier = evt.getCalendarIdentifier();
this.name = evt.getName();
this.days = evt.getDays();
}

/*

*/
}

`

And here is my configuration:

`

@Configuration
@EnableAxon
public class AxonConfiguration {

@Autowired
private ObjectMapper objectMapper;
@Autowired
private DataSourceTransactionManager platformTransactionManager;
@Autowired
private DataSource dataSource;

@Autowired
public void configure(EventHandlingConfiguration config, Environment env) {
if (!Arrays.asList(env.getActiveProfiles()).contains(“test”)
&& !Arrays.asList(env.getActiveProfiles()).contains(“localtest”)) {
config.usingTrackingProcessors(); // default all processors to tracking mode.
}
}

@Bean
public CustomCommandGateway customCommandGateway(CommandBus commandBus) {
return new CommandGatewayFactory(commandBus).createGateway(CustomCommandGateway.class);
}

//====================================
//====================================
// Storage init
//====================================
//====================================

@Bean
public Serializer serializer(ObjectMapper axonObjectMapper) {
// The axon object mapper is configured in the ObjectMapperConfiguration class.
return new JacksonSerializer(axonObjectMapper);
}

@Bean
public EventUpcasterChain upcasterChain() {
// Add Upcaster constructor calls here
return new EventUpcasterChain(
);
}

@Bean
public PersistenceExceptionResolver persistenceExceptionResolver() {
return new JdbcSQLErrorCodesResolver();
}

@Bean
public UnitOfWorkAwareConnectionProviderWrapper springDataSourceConnectionProvider(DataSource dataSource) {
return new UnitOfWorkAwareConnectionProviderWrapper(new SpringDataSourceConnectionProvider(dataSource));
}

@Bean
public JdbcEventStorageEngine jdbcEventStorageEngine(Serializer serializer,
EventUpcasterChain upcasterChain,
PersistenceExceptionResolver persistenceExceptionResolver,
ConnectionProvider connectionProvider,
TransactionManager transactionManager) {
return new JdbcEventStorageEngine(serializer, upcasterChain, persistenceExceptionResolver,
connectionProvider, transactionManager);
}

@Bean
public JdbcTokenStore jdbcTokenStore(ConnectionProvider springDataSourceConnectionProvider, Serializer serializer) {
return new JdbcTokenStore(springDataSourceConnectionProvider, serializer);
}

@Bean
public SpringAggregateSnapshotterFactoryBean springAggregateSnapshotterFactoryBean() {
return new SpringAggregateSnapshotterFactoryBean();
}

@Bean
public EventCountSnapshotTriggerDefinition eventCountSnapshotterTrigger(AggregateSnapshotter snapshotter) {
return new EventCountSnapshotTriggerDefinition(snapshotter, 100);
}

@Bean
public SagaStore sagaRepository(ConnectionProvider springDataSourceConnectionProvider, Serializer serializer) {
// Use the Generic Schema, since it stores the Saga data in a single column instead of using n key/value rows per Saga.
JdbcSagaStore jdbcStore = new JdbcSagaStore(springDataSourceConnectionProvider, new GenericSagaSqlSchema(), serializer);
return new CachingSagaStore<>(jdbcStore, new WeakReferenceCache(), new WeakReferenceCache());
}

@Bean
public Repository calendarRepository(EventStore eventStore, Snapshotter snapshotter) {
EventCountSnapshotTriggerDefinition snapshotTrigger = new EventCountSnapshotTriggerDefinition(snapshotter, 100);
return new EventSourcingRepository<>(Calendar.class, eventStore, snapshotTrigger);
}

//====================================
//====================================
// Individual Saga Configuration
//====================================
//====================================
@Bean
public SagaConfiguration updateCalendarSagaConfiguration() {
return SagaConfiguration.trackingSagaManager(UpdateCalendarSaga.class);
}
@Bean
public SagaConfiguration prepareRemovalSagaConfiguration() {
return SagaConfiguration.trackingSagaManager(PrepareRemovalSaga.class);
}

}

`

Thanks a lot!

Andreas

Hi Andreas,

if you provide a custom repository, it most likely has a constructor where you can provide a ParameterResolverFactory. This is the component that makes sure you can use custom parameters. If you use Axon’s Spring boot auto configuration (or @EnableAxon, which I see active in your code), you have a bean of this type available in your application context. Just autowire it by adding it as a parameter to your @Bean method and pass it to the Repository’s constructor.

Cheers,

Allard

Hi Allard,

thanks, that helped me a lot!

I’ve now created such a Repository for my Calendar Aggregate:

`

@Bean
public Repository calendarRepository(EventStore eventStore, Snapshotter snapshotter,
ParameterResolverFactory parameterResolverFactory) {
SnapshotTriggerDefinition snapshotTrigger = new EventCountSnapshotTriggerDefinition(snapshotter, 100);
GenericAggregateFactory aggregateFactory = new GenericAggregateFactory<>(Calendar.class);
return new EventSourcingRepository<>(aggregateFactory, eventStore, parameterResolverFactory, snapshotTrigger);
}

`

Is it correct for me to manually create the aggregate factory here?

Thanks again,
Andreas

Hi Andreas,

Doing a quick check of the code I’d say using the SpringAxonAutoConfigurer should also specify the AggregateFactory Bean for you.
So my hunch is you can autowire it just like the EventStore, Snapshotter and ParameterResolverFactory.
The name of the bean would be ‘{aggregate-simple-name}AggregateFactory’, so in your example that would be a ‘calendarAggregateFactory’.
So adding the @Qualifier(“calendarAggregateFactory”) to a GenericAggregateFactory parameter should provide you with the aggregate factory.

Hope this helps!

Cheers,

Steven

Hi Steven,

Thank you for your help! Unfortunately, I couldn’t simply inject the ‘calendarAggregateFactory’ since Spring couldn’t find any AggregateFactory beans at all to inject :confused:
It does work when I manually instantiate a GenericAggregateFactory, however I’m not sure if this doesn’t have some unwanted side-effects, since some of my tests seem to fail, when I enable snapshotting.

It looks like some events are published twice to the aggregate right before a new snapshot is created. I’m looking into this at the moment. Do you think this could have something to do with the aggregate factory?

Cheers,
Andreas

Hi Andreas,

No problem! I’m here to help :slight_smile:

Aah and I know why you don’t get a bean.
I’ve taken a closer look to the SpringAxonAutoConfigurer class where the Aggregate beans are created.
If the @Aggregate annotation has a name for the repository it will not automatically create an AggregateFactory for you.
As you’re specifying the repository name in your annotation, the auto configurer thus doesn’t create an AggregateFactory for you.
Sorry for putting you on the wrong path there.

Manually instantiating a GenericAggregateFactory for your Aggregate shouldn’t create any unwanted side effects by the way.
What tests are failing due to enabling the snapshotter? Aggregate Test Fixtures tests or other?

Ah, so ‘events handled twice’ in an Aggregate is probably due to the EventSourcingRepository event sourcing the aggregate prior to performing an action on it (read: handling a command).
Let’s say your aggregate has published 5 events so far, so it’s state consists of those 5 events.
If you then let it handle a new command, the repository for that aggregate will first retrieve the aggregate instance for you.
If that Repository is an EventSourcingRepository, it will thus pull all the events for that aggregate and call the @EventSourcingHandlers to recreate the state of your aggregate.
Once it’s recreated, then the command will be handled by it.
So, my hunch is that that’s what you’re seeing.

If that’s not the case however, then I suggest checking with break points whether the event published twice to the aggregate are both in the exact same Aggregate instance.

Hope this helps!

Cheers,

Steven