Transaction management in projections

Hi,

I see some other behavior regarding to transaction management as I would expect.
The issue is that I have two database schema’s. One for the event store and one for the read model. So there are also two dataSources and two TransactionManagers defined in spring.
To enable the spring / axon auto configuration I defined the datasource for the event store being the @Primary.
To prevent the event store related transaction manager being used in the projections on the readModel side I registered a default transactionManager in the eventProcessingConfigurer.

In my projection service class I’am using these annotations and this works fine:

@Service
@Transactional(transactionManager = "readModelTransactionManager")
@ProcessingGroup(ORGANIZATION_PROCESSING_GROUP)
class OrganizationProjectionService(private val organizationRepo: OrganizationRepo) {

@EventHandler
    fun on(event: OrganizationDetailsUpdatedEvent, @SequenceNumber version: Long) =
        organizationRepo.save(event.toEntity(version))

Question is why is the @Transactional(transactionManager = "readModelTransactionManager") needed, since I registered a defaultTransaction manager in the event processing config?

I would expect Axon to start one transaction for @EventHandler annotated function in the projection service and commit at the end of the function. But when I omit this annotation multiple transactions are started in the event handler. The repository is also annotated with @Transactional(transactionManager = "readModelTransactionManager")

So what is going on, and what is the recommend way to configure this?

Thanks a lot,

Frank

The stack:

  • axon framework (axon-bom-4.9.2)
  • spring-boot 3.17
  • postgres db for event store
  • postgres db for readmodel
  • using tracking event processors

with this Axon configuration:

   @Bean
    fun axonTransactionManager(eventStoreTransactionManager: PlatformTransactionManager) =
        SpringTransactionManager(eventStoreTransactionManager)

    @Autowired
    fun defaultTrackingEventProcessors(
        eventProcessingConfigurer: EventProcessingConfigurer,
        readModelTransactionManager: PlatformTransactionManager,
    ) {
        eventProcessingConfigurer
            .registerDefaultTransactionManager { SpringTransactionManager(readModelTransactionManager) }
            .usingTrackingEventProcessors()
    }

Hi Frank, maybe it’s because of the token store? Is the token store configured to use the readmodel postgres db?

He Gerard,

Thanks for your reply. But no, all axon related tables are in the event store and using the evenStoreTransactionManager. Although that’s what I tried to configure using

    fun axonTransactionManager(eventStoreTransactionManager: PlatformTransactionManager) =
        SpringTransactionManager(eventStoreTransactionManager)

Mentioned above.

But if the token store table is is the event store, it makes perfect sense that you need two transactions. What are the reasons for having the token store table in the event store database? From my point of view the token store should be part of the projection, and updated in the same transaction.

Hi, thats is interesting.

Current our event store schema contains these tables besides job runner tables:

axon_association_value_entry
axon_domain_event_entry
axon_saga_entry
axon_snapshot_event_entry
axon_token_entry
dead_letter_entry

Our @Primary dataSource refers to this event store.

So if I understand correctly you are suggestion that the axon_token_entry table should reside in the read model schema. Is this correct and are there more axon tables which should be moved there?

Indeed, actually, I would expect the axon_association_value_entry, axon_saga_entry, axon_token_entry and dead_letter_entry to reside in the read model, as they are all related to event processors, thus the read side.

Ok, that makes sense. But how to configure axon to use the correct transaction managers for the event store tables and the tables located in the read model?

Since you slit them, you likely need to configure some components yourself, in of relying on the auto configuration. You just need to make sure the correct transaction manager is used for the correct component.