1

We are using quarkus to process messages this run on a regular function in that we have to call a suspend function basically

fun process (msg:Message):Message{
    val resFrom:Data = runBlocking{fetchDataFromDB(msg.key)}
    val processedMsg = processRoutingKey(msg,resFrom)
    return processedMsg

}

We would like to get the data as a Uni (https://smallrye.io/smallrye-mutiny/getting-started/creating-unis) so basically we would like to get back

fun process (msg:Message){
    val resFrom:Uni<Data> = ConvertUni {fetchDataFromDB(msg.key)}
}

We need the uni further downstream one time to process some data but we would like to return a Uni from the method meaning

fun process (msg:Message):Uni<Message>{
    val resFrom:Uni<Data> = ConvertUni {fetchDataFromDB(msg.key)}
    val processed:Uni<Message> =process(msg,resfrom) 
    return processed 

}
jojo_Berlin
  • 673
  • 1
  • 4
  • 19
  • Could you please add some code to the example to show how/where the `Uni` is supposed to be used? This may condition how you start the coroutine that is supposed to provide the `Uni`'s data, and it will show why you need a `Uni` in the first place. What is important in particular is how you control the lifetime of that coroutine. – Joffrey Feb 04 '22 at 13:37
  • @Joffrey I expanded a little more – jojo_Berlin Feb 04 '22 at 13:47

1 Answers1

1

The signature fun process(msg:Message): Uni<Message> implies that some asynchronous mechanism needs to be started and will outlive the method call. It's like returning a Future or a Deferred. The function returns immediately but the underlying processing is not done yet.

In the coroutines world, this means you need to start a coroutine. However, like any async mechanism, it requires you to be conscious about where it will run, and for how long. This is defined by the CoroutineScope you use to start the coroutine, and this is why coroutine builders like async require such a scope.

So you need to pass a CoroutineScope to your function if you want it to start a coroutine that will last longer than the function call:

fun CoroutineScope.process(msg:Message): Uni<Message> {
    val uniResult = async { fetchDataFromDB(msg.key) }.asUni()
    return process(msg, uniResult) 
}

Here Deferred<T>.asUni() is provided by the library mutiny-kotlin. In the examples given in their doc, they use GlobalScope instead of asking the caller to pass a coroutine scope. This is usually a bad practice because it means you don't control the lifetime of the started coroutine, and you may leak things if you're not careful.

Accepting a CoroutineScope as receiver means the caller of the method can choose the scope of this coroutine, which will automatically cancel the coroutine when appropriate, and will also define the thread pool / event loop on which the coroutine runs.

Now, with that in mind, you see that you'll be using a mix of coroutines and Uni at the same level of API here, which is not great. I would advise to instead stick with suspend functions all the way, until you really have to convert to Uni.

Joffrey
  • 32,348
  • 6
  • 68
  • 100