Want to jump right into code? Go check it here.
While reading about Kotlin's coroutines, I thought it would be really helpful
to compare it to other async programming techniques, specifically the
Futures/Promises approach that is widely used in Java.
Even though the Kotlin documentation is pretty good and comprehensive, I felt the lack of a more “real world” example. This is an attempt to fill that gap.
Do not expect to have a comprehensive view of how coroutines work nor how Java’s
CompletableFuture
or Reactor’s Mono
work. The idea is to pick a quite simple made-up problem, implement it
with the different approaches and libraries, and compare them. This is
an introductory material to the topic.
Now let’s get into the made-up problem.
Who never needed to work on a problem where you need to get data from one place, do some processing and send it to a different place? The made-up problem here is basically that.
Assume we work on e-commerce and we want to get the most recent order id from an HTTP endpoint, send this id to two other endpoints that:
Then we need to expose all that information combined to other consumers.
Let’s also assume that the operations after fetching the most recent order are slower on processing and we would like to run them in parallel.
We would like to implement something like the following:
For the future/promises approach we will use two very popular APIs/libraries for dealing with Asynchronous Programming in Java:
CompletableFuture
and Mono
.CompletableFuture
is available in the standard Java library since JDK 1.8 and got some improvements over the last releases. Because it is part of the standard library it is the default choice for Java libraries willing to support non-blocking processes and is used in some other APIs in standard libraries, like the java.net.HTTPClient
.Mono
is the CompletableFuture
equivalent in the Project Reactor library. Project Reactor is a quite popular non-blocking reactive library for the JVM. Its adoption has been growing recently, mostly because of its integration into Spring Framework. It has been adopted by other JVM libraries as well.
Now let’s see how the implementation works with these libraries. First, an implementation using
CompletableFuture
:fun completableFuture(): CompletableFuture<CombinedResult> {
val orderIdFuture: CompletableFuture<String> = fetchMostRecentOrderId()
return orderIdFuture
.thenCompose { orderId ->
val deliveryCostFuture: CompletableFuture<String> = fetchDeliveryCost(orderId)
val stockInformationFuture: CompletableFuture<String> = fetchStockInformation(orderId)
deliveryCostFuture.thenCombine(stockInformationFuture) { r1, r2 -> CombinedResult(r1, r2) }
}
}
fun fetchDeliveryCost(): CompletableFuture<String> { ... }
fun fetchStockInformation(): CompletableFuture<String> { ... }
Now the
Mono
implementation:fun mono(): Mono<CombinedResult> {
val orderIdFuture: Mono<String> = fetchMostRecentOrderId()
return orderIdFuture
.flatMap { orderId ->
val deliveryCostFuture: Mono<String> = fetchDeliveryCost(orderId)
val stockInformationFuture: Mono<String> = fetchStockInformation(orderId)
deliveryCostFuture.zipWith(stockInformationFuture) { r1, r2 -> CombinedResult(r1, r2) }
}
}
fun fetchDeliveryCost(): Mono<String> { ... }
fun fetchStockInformation(): Mono<String> { ... }
They are quite similar, right? That’s exactly why they are here under the same section. They both follow the same async programming technique of Futures/Promises.
You can see that the only difference (apart from the return type) are the methods used, even though they have exactly the same mechanics:
thenCompose
/flatMap
are used to compose two different promises, meaning basically “after this promise is completed, return this other promise”;thenCombine
/zipWith
are used to combine two different promises, meaning basically “give me a new promise that will complete after both promises are completed”.We are not going into details of comparing both libraries, but, for basic use cases, you can see that they follow the same approach.
Kotlin coroutines are in essence lightweight threads. Kotlin language provides one high-level constructs, the suspending functions, and one library, the
kotlinx.coroutines
library, in order to deal with asynchronous programming in a way that is very similar to the regular imperative programming most of the developers are used to.Think of it as the Kotlin way of doing
async/await
that is available in other languages such as C# or JavaScript.Now, back to the topic. Here is how an implementation using suspending functions and constructs in
kotlinx.coroutines
library works:suspend fun coroutines(): CombinedResult = coroutineScope {
//Returns a String directly, no future object
val orderId: String = fetchMostRecentOrderId()
//Note the Deferred type indicating this is a future
val deliveryCostFuture: Deferred<String> = async { fetchDeliveryCost(orderId) }
val stockInformationFuture: Deferred<String> = async { fetchStockInformation(orderId) }
//Awaiting for completion of previous parallel executed fuctions
CombinedResult(deliveryCostFuture.await(), stockInformationFuture.await())
}
suspend fun fetchDeliveryCost(): String { ... }
suspend fun fetchStockInformation(): String { ... }
You can see it seems very similar to the imperative programming style most of us are used to and is how we learn coding in the first place. A few notes:
suspend fun
(lines 1, 13, 14). Suspending functions are basically the Kotlin way of coloring a function in order to advise the compiler that it may run asynchronous operation;coroutineScope
builder;fetchMostRecentOrderId
which returns a String
directly;async
with a suspending function, we can run it on the background, which returns a Deferred object (that is like a promise) as seen in lines 6 and 7;Kotlin’s answer to async/await is slightly different from that on JavaScript. For example, here would be a similar function in JS:
async function jsAsync() {
const orderId = await fetchMostRecentOrderId(); //returns String
const deliveryCostFuture = fetchDeliveryCost(orderId); //returns Promise
const stockInformationFuture = fetchStockInformation(orderId); //returns Promise
return new CombinedResult(await deliveryCostFuture, await stockInformationFuture);
}
async function fetchDeliveryCost() { ... }
async function fetchStockInformation() { ... }
The difference is subtle but important, while Kotlin coroutines are sequential by default, JavaScript ones are concurrent by default:
async
construct, as async functions in JS returns a Promise.This is quite a simple flow just to illustrate the differences in programming style/techniques.
One could argue that for this specific example, the code is cleaner and simple to understand using the Futures/Promise approach, and it would be a matter of style. However, as the code grows more complex and we have more and more requirements things can get quite hard to manage with Futures/Promise.
You can check here for a good post with a more complex example comparing the two approaches.
Or even if we just get the same problem and just try to implement it sequentially things will get complicated with Futures/Promises. As seen here, we needed to introduce intermediate types in order to share context, in this case by using Kotlin Pair type.
I’ve also put together a simple Kotlin Spring Boot project where you can run the code yourself and play around with the different techniques discussed here.
This is a brief introduction to the topic and there is much more to get into the coroutines, for this, I would recommend the following material:
Thank you for reading until here and I hope you enjoyed it.
I would like to give a huge thank you to the friends and colleagues who provided their kind words and early feedback on this post. You all rock!
Previously published at https://medium.com/@danilo_barboza/kotlin-asynchronous-programming-styles-compared-347470b95f3a