Building Production Ready Asynchronous Applications with Kotlin Co-routines
Co-routines, green threads, fibers, user-space threads and more. These are some of the names you might be familiar with if you work with multi-threaded applications. We utilize multi-threading for many different reasons, one such famous example is parallel computing. However, sometimes parallel computing is just not enough, sometimes we simply want our program to perform blocking operations without waiting around doing nothing. Also, we want to do it in an expressive way without unnecessary thread synchronizations.
Cooperative multitasking has been around almost since the dawn of Operating Systems, but it’s been only in the last decade that we started seeing its wide use in programming languages. In this article, we explore Kotlin’s flavor, called co-routines, in the context of the latest Spring Boot release — with Spring Webflux and JOOQ.
It comes as no surprise that request/response services do a lot of I/O.
I/O operations are usually very expensive, in many applications they are the bottleneck, so dealing with them effectively can have a big impact on the overall performance of your services. There are some aspects of I/O you often just can’t change, for example external API response latency or the latency of your database.
However, you can affect how many resources your program wastes waiting around for an answer from the outside world, which is exactly what we try to do with sophisticated multi-threading and reactive programming. Spring Webflux in its latest version allows you to utilize Kotlin co-routines in conjunction with reactive programming. In this article, we discuss these concepts and showcase one of our microservices that uses this setup.
Identity Provider
We will demonstrate all the new concepts in Spring Webflux on our new service called Identity Provider. The job of the service is currently to simply manage authentication to Google API services (Adwords, Google Analytics etc). The application, written in Kotlin, is based on Spring Boot 2.2.0 with Spring Webflux and accesses PostgreSQL database with JOOQ 3.12.
Introduction to Reactive Programming with Spring Webflux
Reactive programming is a concept that allows you to write code in a non-blocking way. Working with kernel-space threads is expensive and it can be error-prone when accessing shared memory, reactive programming solves that by encouraging immutability and limiting the amount of parallelism by reusing threads. Using Spring Functional Endpoints, you can utilize reactive programming by using routers that dispatch work to handler functions which in turn communicate with your business logic (you can also use familiar annotated controllers, but we decided to go with the new approach). An example of such a router is:
An example of a class with handler functions:
The way this reactive code works is quite simple, you receive a request, pass the body as a Mono
object (a Publisher
that emits a single value or none, as opposed to a Flux
that can emit multiple values) to your business logic which returns another Mono
object that you pass as a response. So basically, your business logic does a series of transformations on the Mono
object until it returns the Mono
object that you pass as a response. This allows the reactive core of Spring Webflux to be in control of the execution of the request.
The disadvantage of this approach is that you have a different programming model that is often harder to read and debug. You need to pass around a carrier object which you transform using functions like map
, flatMap
, switchIfEmpty
etc all the way to the DB (or other resources) and back. And stepping through a debugger or trying to understand stack traces can be especially challenging. Using Kotlin co-routines, which are now supported in Spring Webflux, you can do away with carrier objects and combine the best of both worlds.
Introduction to Kotlin Co-routines with Spring Webflux
As Spring Boot 2.2.0 introduced support for Kotlin co-routines, you can now get both the familiar imperative programming model and the advantages of reactive non-blocking programming. The idea is to write suspending functions and use extension functions to convert reactive API to Kotlin co-routine API. That allows you to write the above code like this:
This approach is more readable especially as you start adding error handling, logging, monitoring etc. On the other hand, while you do get rid of the need to wrap your code in Mono
objects, you need to define your functions as suspending. That is a small price to pay, but in the future, when (and if) Project Loom is finished and Java introduces fibers, declaring suspending function might become the past.
Reactive Programming with JOOQ
It is not just spring that is going reactive, but also other familiar frameworks. In version 3.12, JOOQ introduced reactive programming. This allows us to retrieve data from DB like this:
Using some extension functions would make the above code more fluent, but with Kotlin reactive extensions, we might as well use co-routines instead:
Which is much neater, but bear in mind some of the code is still experimental (e.g. method Publisher#asFlow
).
Reactive Programming and Co-routine Challenges
Real-world applications usually depend on more libraries than just Spring and JOOQ, so the question is how to deal with libraries that are not written with reactive programming or co-routines in mind. Unfortunately, there is no simple solution to this, when calling a foreign API that is not reactive, your reactive code might get blocked. You can subscribe blocking code on a separate thread pool with e.g. subscribeOn
or async
which will unblock the event loop or co-routine, but that only moves the blocking code into another thread, which is not a complete solution. When Java introduces green threads, that might not be true anymore, but for now, working with libraries has this limitation.
Another question is the adoption of reactive programming and co-routines in Spring itself. Spring is a big project with many subprojects so, unfortunately, the adoption is not that high. For example, while you can write web filters or spring security reactively, you cannot write them using Kotlin co-routines, so your spring configuration ends up looking like this:
There are several limitations as far as JOOQ is concerned too. JOOQ under the hood still uses JDBC, so it is not fully reactive. In future versions, JOOQ might channel such executions through Spring’s R2DBC or Oracle’s ABDA. Another limitation is that some DB operations cannot be written in a reactive way, for example fetching id of an inserted row into DB:
The reason this code cannot be reactive is because InsertResultStep<R>
class (object of which is returned by method returning
) implements interface RowCountQuery
which extends Publisher<Integer>
, that means you can reactively only get a number of inserted rows.
Lastly, debugging stack traces of reactive programs, even with Kotlin co-routines, can be quite challenging. For example, if there is an error in JOOQ because a column has been dropped but JOOQ generated classes hasn’t been refreshed, you get this rather unhelpful error:
Conclusion
What we have shown is that building asynchronous services with Kotlin co-routines has advanced to the stage that it is viable for production use, at least for a small, relatively simple applications with selected libraries and frameworks.
At the same time, there are many considerations to take into account. Adoption of reactive streams and Kotlin co-routines is still in its infancy and by the time reactive libraries are built, we might get green threads for free with Project Loom. Either way, I expect to see more Kotlin co-routines to be used more at least in the near future, because the benefits they bring are too great to ignore, especially in microservices that are designed to be small and scalable.
Thanks for reading!
➡ Find out our other articles on tech{hunters}.
➡ We are ROI Hunter, if you enjoyed this article, have a look at our job offers.
⬇ Be sure to 👏 below if you like what you read. ⬇