Description
Jose Antonio Iñigo opened DATAMONGO-2632 and commented
Base scenario:
- Spring Boot 2.3.4 Webflux application
- Reactive MongoDB with ReactiveMongoTransactionManager for multidocument transactionality
@EnableTransactionManagement
@Configuration
class MongoDbConfiguration {
@Bean
fun mongoTransactionManager(dbFactory: ReactiveMongoDatabaseFactory) =
ReactiveMongoTransactionManager(dbFactory)
}
The controller is called to create a user :
@PostMapping
fun create(@RequestBody user: UserCreate) = userService.create(User(null, user.name))
UserServiceImpl.create is a transactional method that saves the user and publishes a UserCreatedEvent:
@Service
@Transactional
class UserServiceImpl(private val userRepository: UserRepository,
private val applicationEventPublisher: ApplicationEventPublisher) : UserService {
private val logger = LoggerFactory.getLogger(javaClass)
override fun create(user: User): Mono<User> {
logger.info("create() isSyncActive {} - isTxActive {}",
TransactionSynchronizationManager.isSynchronizationActive(),
TransactionSynchronizationManager.isActualTransactionActive())
return userRepository.save(user)
.flatMap {
applicationEventPublisher.publishEvent(UserCreatedEvent(it))
it.toMono()
}
}
Finally BillingServiceImpl.userCreated is an event listener that checks that the user exists and in this case creates a Billing asociated to that user:
@Service
class BillingServiceImpl(private val userRepository: UserRepository,
private val billingRepository: BillingRepository) {
private val logger = LoggerFactory.getLogger(javaClass)
@EventListener
@Transactional
fun userCreated(event: UserCreatedEvent): Mono<Billing> {
logger.info("userCreated({})", event)
return userRepository.findById(event.user.id!!)
.switchIfEmpty {
UserDoesntExistException(event.user.id!!).toMono()
}.flatMap {
logger.info("*** CREATING BILLING for user {}", it)
billingRepository.save(Billing(null, event.user.id!!))
}
}
There are the two problems I've found:
- Most times UserDoesntExistException is launched, even though in the logs we can see (
Inserting Document containing fields: [name, _class] in collection: sample-user
). Given that the EventListener method failed I assumed the global transaction would be rolled back. However the user is created and stored without rolling back. - Some times the exception is not launched and the Billing is correctly saved. This should be what happened always. Why can't it always see the user created in the same transaction??
I've reference a sample project. You need a MongoDB instance with replication (to support multidocument transactions, e.g.: MongoDb Atlas). Just configure the spring.data.mongodb.uri property form application.yml inside the project.
To call the controller: curl -X POST localhost:8080/users -d '{"name": "John Doe"}' -H "content-type: application/json"
Affects: 3.0.4 (Neumann SR4)
Reference URL: https://github.com/codependent/transactional-event-sample/tree/event-listener