Asynchronous Programming Techniques with Kotlin

Written by dbarboza | Published 2020/05/29
Tech Story Tags: kotlin | asynchronous | java | reactive-programming | coding | programming | code-quality | software-engineering

TLDR An introduction to Kotlin coroutines compared to Futures/Promises. This is an attempt to fill that gap. The idea is to pick a simple made-up problem, implement it and compare it to other methods. Java’s CompletableFuture and Project Reactor are similar to the Futures and Promises approach used in Java. Kotlin's implementation is based on an e-commerce e-mail problem. The problem is to get the most recent order id from an HTTP endpoint and send this id to two other endpoints.via the TL;DR App

An introduction to Kotlin coroutines compared to Futures/Promises such as Reactor’s Mono or Java’s CompletableFuture

Want to jump right into code? Go check it here.

Introduction

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.

Problem Statement

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:
  • Give us the stock information about the items in the order, and;
  • Give us the calculated delivery cost for the order.
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:

Futures/Promises approach

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.

Coroutines 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:
  • All functions are suspending functions, as seen by the keywords
    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;
  • Suspending functions can only be executed from within another suspending function or a
    coroutineScope
    builder;
  • Line 3 is suspended, meaning execution will be suspended (non-blocking!) waiting for the return of
    fetchMostRecentOrderId
    which returns a
    String
    directly;
  • By using
    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;
  • Line 10 is suspended again until the result of both Deferred objects complete, by calling the await function on the Deferred objects;
  • We need to wrap the function into a coroutineScope in order to do parallel processing with async as coroutine code is executed sequentially by default.
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:
  • On line 2 we need to explicitly use await, while in Kotlin there’s no need to do so;
  • On lines 4 and 5 there’s no need to use any
    async
    construct, as async functions in JS returns a Promise.

Conclusion

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!

Written by dbarboza | .
Published by HackerNoon on 2020/05/29