quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.57k stars 2.63k forks source link

@ReactiveTransactional support Kotlin suspend function #25563

Open RyanWang0811 opened 2 years ago

RyanWang0811 commented 2 years ago

Description

I use quarkus with kotlin suspend function to write the reactive programming.

When I try to use quarkus-hibernate-reactive-panache and make the method in a transaction boundary by using @ReactiveTransactional like this

  @POST
  @Path("/suspend/withReactiveTransactionalAnnotation")
  @ReactiveTransactional
  suspend fun createFruitSuspendWithReactiveTransactionalAnnotation(fruitRequestDTO: FruitRequestDTO): FruitPO? {
      return fruitService.createFruitSuspendWithReactiveTransactionalAnnotation(fruitRequestDTO)
  }

https://github.com/aotter/quarkus-examples/blob/8f4292c7ac0f4a2af70836d7bcbc28f04902b6e4/hibernate-reactive-panache-quickstart-kotlin/src/main/kotlin/net/aotter/FruitResource.kt#L56-L61

I got the exception

only Uni is supported when using @ReactiveTransaction if you are running on a VertxThread

Is it possible to let the @ReactiveTransactional support kotlin suspend function?

Implementation ideas

No response

quarkus-bot[bot] commented 2 years ago

/cc @evanchooly

mschorsch commented 2 years ago

@geoand Are there any plans to support suspend functions for @ReactiveTransactional?

geoand commented 2 years ago

We certainly should at some point.

murphye commented 1 year ago

+1 on this one. It's a pain point for using Kotlin with Panache Reactive as you are forced to go back to using the reactive APIs for anything transactional

Otherwise using suspend functions with Quarkus Reactive is a good experience.

geoand commented 1 year ago

I'll try and have a look soon

geoand commented 1 year ago

I thought we had coroutine support for Hibernate Reactive, but that seems to not be the case @evanchooly ?

geoand commented 1 year ago

@murphye @mschorsch how exactly do you envision you would use @ReactiveTransactional with a suspend function? Do you have examples?

murphye commented 1 year ago

@geoand I am able to use Coroutines with Hibernate Reactive. This can be done with https://smallrye.io/smallrye-mutiny/1.6.0/guides/kotlin/#awaiting-a-uni-in-coroutines.

A simple (contrived) example where there is a problem with @ReactiveTransactional and Coroutines. In this case, customerRepository is a Hibernate Reactive Panache repository with a persist() that returns Uni<Customer>.

    @POST
    @ReactiveTransactional
    suspend fun persistAndReturnId(customer: Customer): Long? {
        // Do some transactional stuff

        val customerPersisted: Customer = customerRepository.persist(customer).awaitSuspending()
        // Do something with customerPersisted

        val customerKafkaResult = emitter.send(customerPersisted).awaitSuspending()
        // Do something else with customerKafkaResult

        // Persisting customer and sending to Kafka were both successful
        return customerPersisted.id
    }

If you were to try to use this code, you would get this error: only Uni is supported when using @ReactiveTransaction if you are running on a VertxThread because it's a suspend fun.

There might need to be a new annotation such as @SuspendTransactional rather than try to make @ReactiveTransactional work in this context.

evanchooly commented 1 year ago

I thought we had coroutine support for Hibernate Reactive, but that seems to not be the case @evanchooly ?

I don't think anyone's taken a look at that specifically. It's relatively new so no one's gotten to it yet.

geoand commented 1 year ago

@murphye thanks for the snippet.

In your example you could essentially do what you want with: Panache.withTransaction(() -> customerRepository.persist(customer)).

I am mentioning this because you are already working with a Mutiny API. I think the annotation support would only make sense if we had a suspend API for Hibernate Reactive.

akoufa commented 1 year ago

@geoand I think it would also make sense for the current state of Hibernate Reactive. As @murphye showed with his snippet we can easily use awaitSuspending() to move from Hibernate Reactive Mutiny to the Coroutines world.

Of course, if the code needed to be in a single transaction is in the same method, it is straightforward to wrap it around a withTransaction block either using Panache or plain Hibernate Reactive. But if the code needed to be in a transaction is in different methods of different components then having to use withTransaction is a little cumbersome and @ReactiveTransactional would make sense, imho.

For example:

class OrdersService(private val userRepository: UserRepository, private val ordersRepository: OrdersRepository) {

    @ReactiveTransactional
    suspend fun createOrderForUser(user: User, order: Order) {

        userRepository.createUser(user)
        ordersRepository.createOrder(order)
    }
}

As the above example does not work yet with Coroutines I have to introduce withTransaction in my Service Layer, which unfortunately leads to not simple and very readable code

geoand commented 1 year ago

@akoufa thanks for the input.

But with the current state of things userRepository.createUser(user) is not a suspend function, is it?

akoufa commented 1 year ago

@geoand Yes it is a suspending function. Internally in the createUser(user) method a call to Panache or Hibernate Reactive is made and then awaited with awaitSuspending . So we can have a fully Kotlin Coroutines solution with Uni hidden only in the Data Layer components without being exposed in the APIs.

geoand commented 1 year ago

I suppose we could have a new annotation named @SuspendTransaction (or something) that would essentially call HR's transaction APIs in the proper sequence...

andreas-eberle commented 1 year ago

@geoand, Just out of curiosity: Why would you need a different annotation? Could it not just work with the normal @ReactiveTransactional and determine the implementation based on the function's type (Uni result vs suspend function)?

geoand commented 1 year ago

It could potentially be the same annotation but it would be a lot more involved than what you describe

andreas-eberle commented 1 year ago

OK. Sorry, I assumed it is quite complex but was just wondering about this. Thanks for all your efforts you put into Quarkus and I'm totally looking forward for this feature :)

geoand commented 1 year ago

Sure, no problem.

This definitely requires more thought, but it certainly seems like an interesting thing to have.

murphye commented 1 year ago

I am mentioning this because you are already working with a Mutiny API. I think the annotation support would only make sense if we had a suspend API for Hibernate Reactive.

Just a note. There does exist a Spring CoroutineCrudRepository.kt interface with a Mongo implementation. It's something to consider when it comes to Reactive Panache.

However, I am not sure how much value a Coroutine Repository really has, as I would rather use one Reactive Repository interface and decide myself whether I want to use a suspend fun or not depending on the implementation context. It should not be either one or the other. It's easy enough to use .awaitSuspending() when I want to.

That being said, the @ReactiveTransactional support is still needed for suspend fun per my example above.

geoand commented 1 year ago

Indeed, thanks for the update

geoand commented 1 year ago

One problem of having something like @ReactiveTransactional is that we would likely only be able to support placing it on known coroutine execution entrypoints - like JAX-RS methods, and not in arbitrary places like one can do with @Transactional. kotlin.coroutines.ContinuationInterceptor might be useful, but I didn't find any good examples with a quick search.

tinyzero4 commented 1 year ago

That will be awesome if reactive transactions could be managed in the following way

    @ReactiveTransactional
    override suspend fun createUser(command: SignUpRequest): AuthSessionFacts {

        val facts = userService.createUser(command) // suspended execution

        createPersonalProfile(command, facts) // suspended execution

        val session = authSessionService.createSession(facts.user, command.deviceId) // suspended execution  

        eventBus.publish(SIGNUP.name, UserEventData(facts.user, session))

        return facts
    }
andreas-eberle commented 1 year ago

One problem of having something like @ReactiveTransactional is that we would likely only be able to support placing it on known coroutine execution entrypoints - like JAX-RS methods, and not in arbitrary places like one can do with @Transactional. kotlin.coroutines.ContinuationInterceptor might be useful, but I didn't find any good examples with a quick search.

@geoand : Why is that? Aren't you already detecting all @ReactiveTransactionals with a uni response type? Wouldn't it be possible to extend this to detect if it is a coroutine and then wrap it with code to handle the transaction?

geoand commented 1 year ago

That wouldn't be enough

elgabbouch commented 1 year ago

Any update about this feature?

geoand commented 1 year ago

Nothing new to report

On Thu, Sep 14, 2023, 23:48 Mohamed El Gabbouch @.***> wrote:

Any update about this feature?

— Reply to this email directly, view it on GitHub https://github.com/quarkusio/quarkus/issues/25563#issuecomment-1720132900, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABBMDP43Y2T7MQLIUXD7LNDX2NUTHANCNFSM5V3PXQHQ . You are receiving this because you were mentioned.Message ID: @.***>