[Axon 3-Spring Boot] Transaction Management

Hi all,

I have a question about the transaction management when using the default Axon configuration with Spring Boot.

I can see that the SpringTransactionManager is correctly attached to the Command Bus, so far so good.

So now, I have some events handlers on the read side configured by default as Subscribing processors.
For testing purpose, I’m throwing a Runtime exception in one of the Event Handler, expecting the transaction to be rolled back ( no event and no other entities handled on the read model to be persisted).

Instead, I get a message saying that the event handler failed and “Continuing processing with next listener” but the event is correctly persisted to the event store ( and all other event handlers successfully process the event).

Could you please enlighten me about the default strategy ? And how could we fine tune this configuration ?

As far as I remember, in Axon 2, the default behavior was the one described above, but I might be wrong.

Thank you,
Best Regards,
Jerome

Hi Jerome,

From your description it sounds like you’re running an Axon application with the default config.

Thus, the thread publishing the command is also the thread handling the command, applying the event, handling the event in the Event Processor and handling the event in the Event Listener.

By default, an exception in an Event Listener, so the bean which you’ve annotated with @EventHandler functions, will not break the overall execution of your Subscribing Event Processor.

It thus not perform a rollback of the overall UnitOfWork, but only for that listener.

As such your event will be persisted in that scenario.

Additionally, I’d argue it’s desirable that an exception in your query side does not block the persisting of the event in your store.
It’s not the event storage, your command model, which went wrong in that scenario, it’s you’re query model which failed.

If you would however want to adjust the exception handling behavior in your Event Listeners, you should provide ListenerInvocationErrorHandler.

If you’re in a Spring environment, you can simply add a bean of your own ListenerInvocationErrorHandler, which will automatically get picked up by your Event Processors.

Hope this helps you out Jerome!

Cheers,

Steven

Hi,

Is there any other configuration required to achieve a rollback of the eventstore in case of a query model failure ? I have put together a very basic application with this configuration, but the eventstore commits instead of rolling back when an @EventHandler throws an exception.

`

@Bean
AggregateConfigurer configureProcessAR() {
return AggregateConfigurer.defaultConfiguration(TheAR.class);
}

@Bean
EventStorageEngine eventStorageEngine(DataSource dataSource,
TransactionManager transactionManager) {
JdbcEventStorageEngine jdbcEventStorageEngine = new JdbcEventStorageEngine(
new SpringDataSourceConnectionProvider(dataSource), transactionManager);
// because the jdbc event storage engine does not create the tables automatically
jdbcEventStorageEngine.createSchema(HsqlEventTableFactory.INSTANCE);
return jdbcEventStorageEngine;
}

@Bean
PropagatingErrorHandler propagatingErrorHandler() {
return PropagatingErrorHandler.instance();
}

@Bean
SimpleCommandBus commandBus(TransactionManager transactionManager) {
// instantiate the commandbus to add a message monitor
SimpleCommandBus simpleCommandBus = new SimpleCommandBus(transactionManager, messageMonitor());
simpleCommandBus
.registerHandlerInterceptor(new TransactionManagingInterceptor<>(transactionManager));
simpleCommandBus.setRollbackConfiguration(RollbackConfigurationType.ANY_THROWABLE);
return simpleCommandBus;
}

`

The command is sent transactionally, so i expected all axon components (given above config) to participate in the same transaction and have a global commit or rollback.

`

new TransactionTemplate(platformTransactionManager)
.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
commandGateway.send(command);
}
});

`

Thanks,
Jorg

It seems sendAndWait on the command gateway produces the ‘desired’ behaviour, which makes sense now that i think about it.

The following would also work, i guess semantically it’s the same as doing sendAndWait

`

new TransactionTemplate(platformTransactionManager)
.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
FutureCallback<TheCommand, Object> callback = new FutureCallback<>();
commandGateway.send(command, callback);
callback.getResult();
}
});

`

Jorg

Hi Jorg,

As a heads up I’d like to point out that what you describe here will only work if you’ve got a Axon-app with default configuration.

As soon as you’re using an asynchronous mechanism of handling your events (e.g. the AsynchronousEventProcessingStrategy on the Subscribing event processor or the TrackingEventProcessor), a failure in event handling will not rollback up to the command publishing.

Cheers,

Steven