1

In our slow transition from typical synchronous/imperative programming to being fully reactive in our spring boot projects, we have introduced reactive repositories (dependency on spring-cloud gcp firestore in particular), the usual...

@Repository 
public interface BookRepository extends FirestoreReactiveRepository<Book> {}

So when our reactive code meets the usual spring boot code, we currently have a lot of block() and blockOptional() calls, but all such calls are isolated in there own service...

public class ReactiveRepositoryBridgingService {

  private final BookRepository bookRepository;

  @Autowired
  public ReactiveRepositoryBridgeService(BookRepository bookRepository) {
    this.bookRepository = bookRepository;
  }

  public Optional<Book> findCurrentBookById(String bookId) {
    return bookRepository.findById(bookId).blockOptional();
  }

....

and, then of course we have the need to do some transactional operations in the service, something like,

  public Book decrementCountOnCurrentBook(String bookId) {
    return decrementCountOnBook(bookId).block();
  }


  @Transactional
  public Mono<Book> decrementCountOnBook(
          String bookId) {
    return bookRepository
            .findById(bookId)
doOnNext(
                    b ->
                            log.info(
                                    "Decreasing book count for id {}, fromCount: {} -> toCount: {}",
                                    b.getId(),
                                    b.getCount(),
                                    b.getCount() - 1))
            .flatMap(
                    b -> {
                      b.setCount(b.getCount() - 1);
                      return bookRepository.save(b);
                    })
            .doOnError(ex -> log.error("Failed to decrement book count: {}", ex.getMessage()));
  }

The blocking method (decrementCountOnCurrentBook) cannot be annotated with @Transactional since its not returning a publisher type, and as I understand from the spring docs, ReactiveRepositories and TransactionManagement is enabled by default by way of the spring boot starter dependencies.

And herein lies the problem we see. When we receive multiple requests on our standard WebMVC controller (not using webflux), there are multiple invocations of the transactional reactive methods, since I guess they are being scheduled on multiple threads, and therefore interfere with each other (some naivety here on what could be actually happening)...

So occasionally we would receive 2 bookupdate requests on the controller in quick succession, and then multiple log entries:

13:31:06.948 [main] INFO Decrementing book count for id A1-XYZ, fromCount: 4 -> toCount: 3
13:31:06.963 [main] INFO Decrementing book count for id A1-XYZ, fromCount: 4 -> toCount: 3

So what is happening here, and how do we get around this?

UPDATE: Found out what was really going on via stackoverflow.com/questions/3423972/…

After following advice there, I can now see that the reactive methods are truly transactional and throwing Rollback exceptions, due to other reasons (probably due to Firestore limits)

Englishbob
  • 197
  • 1
  • 11
  • Beginning to think that our transactions are not working or enabled, since Firestore, and I guess, therefore, the spring boot reactive repositories should uphold serializable isolation for transactions. – Englishbob Jul 13 '21 at 14:55
  • 1
    It's probably not a reactive specific issue. See: https://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo – Martin Tarjányi Jul 13 '21 at 19:34
  • @MartinTarjányi Thankyou for the pointer. This was the problem indeed. Moving all transactional methods to their own class (as per solutions in the link you gave) worked. I can now see the exception thrown upon an actual rollback of one of the transactions. – Englishbob Jul 14 '21 at 10:06

0 Answers0