Kotlin as an option for service creation at Gympass
Introducing the Kotlin language as an option for service creation at Gympass.
Authors: Rafael Pereira Santos, Rafael Sienna, Diego Caxito
Abstract
Some months ago, we at Gympass began to see that some parts of our code, written in Ruby on Rails , were large and complex. This demanded a split from a much bigger service we had at that time to a smaller one, a microsservice specific to these roles (and if you were thinking of a monolithic transition to a microsservice — you’re right).
We began discussing what alternative language and what stack could be used in this new service we were planning to build. Some of them were already well known by the team, such as Ruby on Rails , Scala with Akka and NodeJs , while others came from personal experience by team members, like Kotlin , and others just piqued our curiosity, like Typescript .
In this post we will describe our perspective on our ecosystem and how our engineers worked to introduce this new stack and these new technologies in order to improve their own working life and the overall quality of our product.
In the beginning…
At that time the mainstream languages were Ruby and Scala. Our entire team was well versed in these languages and their stacks and we thought it would be no problem to implement a new service with it. We also thought, though, that this would be a great opportunity to try something new, so we took a shot and began exploring new options. The ones that surfaced were Kotlin, Typescript and Go. The first two were recommended by members of the migration team, while the last one was making inroads at Gympass and was, at that time, relatively new .
After some tests and discussions among team members, we began to consider Kotlin together with Spring Boot (alongside other smaller frameworks and libs that will be discussed later) as a candidate.
In order to decide what languages and stacks would be used, we defined a few criteria: all had to be easy to learn and not that different to what we had at the time (in order to create an entire rest service in C) and they had to perform at the very least no worse that the stacks we had. We also set on a process to ensure a new stack was sustainable and properly automated.
Productivity
Another point that supported our decision was the fact that we already had some know-how and experience with the Java world, so there would be a less steep learning curve using Kotlin if compared to Scala or Go. And even those team members that were not experienced would be able to learn easily because of the shallow learning curve and the support of the Spring Boot community both in Kotlin and Java.
In order to try it out, as shown above, we decided to start a test project from scratch in order to learn if we could create a simple RESTful application with ease. One of our first observations was that it was very easy and quick to create, using the Spring Boot framework, because of its philosophy of convention over configuration . Even so, when trying to drop the configuration, which can be desirable in some cases, the configuration is simple enough that it hardly warrants any problem.
Thanks to this philosophy, we have a lot of plug ‘n play libraries, usually with the starter suffix, that just by a simple dependency declaration does all the needed setup related to this aspect of the application. A good example of these are the , Spring Webflux and libraries, which allow us to serve with minimum boilerplate reactive endpoints, or endpoints like / Spring Admin health and / env Spring Actuator and / metrics , or helping us monitor the state of our application more thoroughly.
Even better, we already have Actuator and in place within the Gympass ecosystem. Through our internal , we can check the application status and perform some administrative tasks such as changing the log level, extracting the thread dump, cleaning the cache, etc. It’s all available by just adding a couple of dependencies .
Cost & Efficiency
Inside Gympass
As mentioned before, we already have an application in production with Kotlin and Spring Boot, with an following it . It seems that the most requested endpoint from the application ( GET /partners/presentation ), has a very low latency, with a P95 of 17ms on the week of June 24th, 2020.
If needed, we can see the memory footprint of the application inside the containers view:
As can be seen, the memory footprint isn’t really small but it is really manageable, using a lot less than 1GB, the limit of a t2.micro EC2 instance . Initially we thought it was significantly more than Scala, but when searching the data about other Scala applications that use Postgres too, the datastore used by partner-content-api, we found services from 400MB to 800MB:
Looking at this data, the memory footprint of the services seems to be comparable, although we can imagine that the Spring Boot applications will have a minor extra memory consumption if we don’t do any tuning. And as an outlier, the eligible service uses4 to 5 GB of RAM, but it is an extreme case that deals with very large files.
Benchmarks research
Frameworks
We also had to decide what framework to use with Kotlin. We performed some internal benchmarks, but there are some good ones available out there — , this one measures Kotlin using different frameworks including Spring Boot. You can watch this , alongside this . It explores some aspects, like boot time, memory consumption and response time in the JVM: repository
Spring Web MVC and Webflux
- Servlet Blocking
- Servlet with CompletableFuture Java 11 Client
- Servlet with CompletableFuture Apache Client
- Webflux with Java HttpClient
- Webflux with Apache HttpClient
- Webflux with Spring Webclient
As can be seen, the tests were divided between 3 different implementations using Web MVC(Servlets) or Webflux. For each setup there was a battery of tests with a parameterized concurrent client number, with an IO call that took a parameterized delay time. The implementations had the following throughputs:
If the image is unclear, please refer to the link under the image. In summary, it shows that in most of the cases, the Webflux versions prevail over the Web MVC versions of the application, except for those that use Java 11 HttpClient on Webflux. It shows that the use of Webflux with Apache HttpClient and Spring WebClient are comparable in question of throughput. It can be seen too that the throughput is smaller than the one given in the benchmark above. This can be explained with two details: this current benchmark does an IO call in the middle of the request, when the benchmark above is purely CPU bound, without json serialization.
Another detail this benchmark looks at is the memory footprint, with almost 60 charts. We won’t show them here, but in short, they demonstrate that the applications don’t vary too much in memory footprint with the scaling of concurrent users (in tests with 100, 200, 400 and 800 concurrent users).
To summarize, if we are interested in the most responsive application, it would be in our interest to select the completely reactive alternative like Webflux, rather than a Servlet one, or even a Servlet alternative using reactive elements, like CompletableFuture. When comparing a completely reactive approach to a Servlet with reactive calls approach, it seems in cases of low IO delay, the throughput can become 25% lower. In cases of more IO delay inside the requests, the difference becomes surprisingly smaller.
It is also important to quote here that it seems that at the time of that benchmark, the Java 11 HttpClient had serious performance issues inside Webflux, and sot seems that it is more secure to use third party libraries for HTTP, like Webclient, Apache HttpClient or, missing in this benchmark, OkHttp.
JDBC and R2DBC
Another interesting benchmark is (please try opening it in Chrome, as Firefox returns a 403 error) , comparing applications with a matrix of R2DBC and JDBC with Spring Webflux and Spring Web MVC.
In this benchmark, the writer tested the applications in increasing steps of concurrency, starting with 4 concurrent requests and evolving until 500 concurrent requests. In those tests, he measured for each combination:
For the sake of brevity, we will not show all the graphs here, only the throughput and the latency charts, but if more detail is desired, we recommend reading the complete article:
Observing those resources shows that the latency is better in any setup that uses R2DBC, achieving significant gains in their use. It also shows that in matters of latency, the use of Webflux is undisputed.
When looking at the throughput, it is confirmed that for a scalable system, R2DBC continues winning in one more question. Even so, the throughput chart shows that the combination of JDBC and Spring Webflux is worryingly less scalable and has a surprising drop in performance, even with its superior latency. It makes this combination hard to suggest in some cases.
Even with those demonstrable gains, there are even further issues, including this challenge when choosing R2DBC:
“ When Java Fibers will be introduced (Project Loom, could be Java 15), the driver landscape might change again and R2DBC might not become JDBCs successor after all.”
This could cause the problem of us having to revert back to another implementation in the future and although there is always hope that Spring will ease our lives and create another library that will allow us to change with minor changes, this is not yet confirmed.
Market adoption
Stack Overflow Language Trending: Kotlin, Scala and Go
According to Stack Overflow, these are the trends of the current languages mostly used by Gympass in Backend, Go, Scala and Ruby, and Kotlin:
Stack Overflow Frameworks Trending: Spring-boot and Akka
According to Stack Overflow, these are the trends of the current frameworks mostly used by Gympass in Backend, Akka and Ruby on Rails, and Spring Boot:
TIOBE has an index that excels in demonstrating interest in languages, as they rank languages by search queries in various search engines. Kotlin is ranked in 27th place:
We could find that the language is growing as referenced from the TIOBE Index for July 2020 :
“ […] Other interesting moves this month are Rust (from #20 to #18), Kotlin (from #30 to #27) and Delphi/Object Pascal (from #22 to #30)” .
Github State of the Octoverse 2018–2019
Thoughtworks Technology Radar — Kotlin
In 2018, Kotlin achieved the Adopt state in , indicating that Kotlin development was mature enough to be suggested as a new technology for development: “[…] With JetBrains adding the ability to compile Kotlin to Thoughtworks Technology Radar on multiple platforms, as well as , we believe it has the potential of much wider use by the larger community of mobile and native application developers.” native binaries transpile to JavaScript
Thoughtworks Technology Radar — Spring Boot
In 2016, Kotlin achieved the Adopt state in , indicating that Spring Boot development was mature enough to be suggested as a new technology for development: “A lot of work has gone into Thoughtworks Technology Radar to reduce complexity and dependencies, which largely alleviates our previous reservations. If you live in a Spring ecosystem and are moving to microservices, Spring Boot is now the obvious choice. […]” Spring Boot
Kotlin FAQs
Looking at Kotlin FAQs , we can find the following declaration: “ There are too many companies using Kotlin to list, but some more visible companies that have publicly declared usage of Kotlin, be this via blog posts, GitHub repositories or talks include , , or .”
After looking at the quantity of resources, there are a lot of other sources that indicate the same interest in Kotlin:
It seems that Kotlin is very well positioned in the majority of those rankings, and although sometimes it might be surpassed by languages like Go and Scala, it seems more consistent in those indexes.
Note, however, that Java is always near the top of every ranking, alongside Javascript. Kotlin isn’t Java, but a Java developer could easily adapt to a Kotlin environment as it has a direct interoperability model with Java.
Existing toolbelt to solve daily problems
Considering that we already have one technology stack up and running, we researched frameworks and libraries that could be adopted quickly within the stack to solve our daily routine problems during our services building. Below is a list with samples that we could find on this stack toolbelt to work on our daily routine. This list is dynamic, of course, and could change as our engineers begin to develop their components.
Kafka
- Kafka official examples are all made in Java, so using Kotlin is no problem.
Swagger
- There are plenty of ways to implement Swagger using Spring Boot, and this one this one is a good example
Datadog
- There is already step-by-step documentation about how to implement datadog in a JVM based application.
Dynamo
- Amazon has an official documentation for integrating Dynamo and Java.
Elasticsearch
- Elasticsearch has a dedicated page for supported clients.
- There is a specific client for Kotlin: ES Kotlin, that allows the user to use their DSL to better use Elasticsearch.
Postgres/MySQL
- There is a nice looking library called Exposed that seems to address a bunch of integrations.
- We have the Spring Data JPA library, which allows us to create easy repositories with a minimum boilerplate. The only possible drawback is the fact that it works only in a blocking sense.
- For a reactive SQL client, we have access to Spring Data R2DBC, which allows us to query MySQL and Postgres databases in a reactive manner, integrating better with Webflux. It is a little more verbose than JPA, but in case of a full reactive flow, it can be of interest.
REST Client
- Retrofit is a library created in Java that gracefully attends this need, you can see examples of this use in the Kotlin spike in link and resources. This library supports blocking calls by default and reactive calls via coroutines, callbacks or, by means of adapters, Spring Reactor abstractions. In using Retrofit, we use the OkHttp Http client underneath the hood.
Testing
- JUnit Jupiter will be our test engine.
- JUnit 5 is a good option for a more traditional style of unit tests, running over the JUnit Jupiter engine.
- For more complex integration tests, we can use the Spring Test Starter library, which works with JUnit 5 and automatizes to great lengths the process of configuring the environment, as can be seen in this tutorial.
- For those accustomed with Rspec and Spec, there too exists Spek, a BDD style test framework, running over the JUnit Jupiter engine.
Some other frequently asked questions that we faced during the request for comments fase
Why not Micronaut?
Micronaut showed great promise during our research, with gains of memory footprint and throughput. Our discussions showed that we could see potential gains, but using Micronaut would deprive us of using all the know-how on Spring Boot available on the internet and on the market.
Because of this, we chose to use Spring Boot as the framework, so we could choose a path that will allow us to move as fast as possible with a great degree of quality, in a way that will be welcoming to every new developer approaching the stack for the first time. And although it is not the most efficient route, we believe that the gains in developer productivity will outperform the gains in memory footprint and response time.
We might discuss this approach in the future, depending on the evolution of Kotlin inside Gympass, but the decision seems like the correct one for now.
Spring Webflux or Spring Web MVC? Blocking or not Blocking?
At first, it seems like Webflux is the obvious choice, a framework that seems to outperform Web MVC in every aspect, even when the Spring Web MVC vs Spring Webflux issue is raised.
Even so, analyzing the two worlds, it seems that not only there is a lot of overlap between the two libraries, but Webflux has some drawbacks too:
It seems that Spring MVC works best with libraries that are mandatorily blocking, like JPA, JDBC and others. Not only that, but a non reactive code is a code easier to understand and debug, as reactive code can be running on a thread pool with an infinitude of threads over an event loop. For both Reactor and coroutines, we have stack traces of diminishing investigative power, lowering the transparency of how the application is running and how errors happen. There is even the risk of some Spring functionalities not existing inside Spring Webflux but existing inside MVC, and the process of filter creation being harder inside the Webflux environment. It seems like Web MVC is a more beginner friendly library than Webflux.
Outside of that, some very important aspects to be in favor of using Webflux. By using Webflux we gain access to the creation of routes via a routing DSL that reminisces Akka routes model. And just by using not only Webflux, but libraries that do IO in a reactive manner, we gain APIs that scale better with more concurrent users, which can be game changing in projects that have a high degree of concurrent requests.
After listing those arguments of both web frameworks, it seems that the balance between the two is closer than at first sight. Even so, one would argue that Webflux by default seems to already offer throughput gains over Web MVC just by being used, outside of cases that use JDBC, as shown on our benchmarks. There is the fact that Webflux allows the user to use the @Controller syntax already popular with Web MVC, diminishing the gap between the two. We could even say that we can make the same blocking endpoint in both with minimal differences this way, and that sometimes it is a valid choice, as the complexity, debugging and error discovery of endpoints.
- A request that is purely CPU bound? Imperative.
- A request rarely called, with little or no IO and complex logic? Imperative with a beginners team, reactive with a more experienced team.
- A request that is moderately called, with IO inside it and complex logic? Imperative with a beginners team, reactive with a more experienced team.
- A request that is moderately called, with IO inside it and no complex logic? Reactive.
- A request that is known to be a bottleneck, with any non zero quantity of IO inside it? Reactive.
It seems that the best choice is defaulting to Webflux and using the simplest way to solve the given task, as we must consider not only the performance, but how much it needs someone with a higher level of know how to understand what is happening and maintaining it:
This choice is not as clear cut as the case of use of Spring JDBC/JPA instead of R2DBC in cases of MySQL/Postgres access, because it shows that MVC outperforms Webflux in those cases.
Conclusion
To summarize, after all this research, we decided to begin adopting Kotlin with Spring Boot as one more alternative to creating services within the Gympass product development team and to start using it as a fresh new service.
All of the content and research shared here is just a small sample of what Kotlin offers. It should meet what we need at Gympass, but there isn’t really a perfect language or framework that would meet all of our development issues. Kotlin meets some of the requirements looked at when choosing a language.
This post was the result of an entire team of developers from different areas coming together to contribute with ideas and proposals. We would like to thank Gabryel Monteiro in particular for his willingness to share his expertise with Kotlin and pointing it out as an alternative to us.
Links and resources
- Partners discovery of the language — https://tinyurl.com/y4nz3sme
- Spring Webflux — https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html
- Spring Boot Admin — https://www.baeldung.com/spring-boot-admin
- Spring Boot Actuator — https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html
- Kotlin 2019 — The State Of Developer Ecosystem — https://www.jetbrains.com/lp/devecosystem-2019/kotlin/
- Thoughtworks Technology Radar — https://www.thoughtworks.com/radar/languages-and-frameworks/kotlin
- TIOBE index about languages — https://www.tiobe.com/tiobe-index
- Stack Overflow — Trends between Ruby, Go, Scala and Kotlin — https://insights.stackoverflow.com/trends?tags=kotlin%2Cscala%2Cgo%2Cruby
- Stack Overflow — Trends between Ruby on Rails, Akka and Spring Boot — https://insights.stackoverflow.com/trends?tags=akka%2Cspring-boot%2Cruby-on-rails
- Memory usage of the partner-content-api — https://app.datadoghq.com/containers?graphMode=group&text=partner-content
- Spring Boot + Kotlin Benchmark — Youtube — https://www.youtube.com/watch?v=rJFgdFIs_k8&feature=youtu.be
- Spring Boot + Kotlin Benchmark — Github — https://github.com/graemerocher/framework-comparison-2020
- Spring Web/Webflux Benchmark — https://tinyurl.com/y3vzv5rt
- Spring JDBC/R2DBC Benchmark — https://tinyurl.com/y67yx87j
- Github State of the Octoverse 2018–2019 — https://octoverse.github.com/#footnote--fastest-growing-languages
- Github Language Stats — 2020 — Quarter 2 — https://madnight.github.io/githut/#/pull_requests/2020/2
- PYPL PopularitY of Programming Language — http://pypl.github.io/PYPL.html
- Kafka on Java Examples — https://github.com/apache/kafka/tree/trunk/examples/src/main/java/kafka/examples
- Spring Webflux Docs — Webflux or Web MVC — https://tinyurl.com/yygjydq4
- Applying Swagger on Spring Boot — https://tinyurl.com/y5f6v85a
- Datadog Setup on Java — https://docs.datadoghq.com/tracing/setup/java/
- Dynamo on Java Examples — https://tinyurl.com/yxkga57m
- Elasticsearch List of Libraries — https://www.elastic.co/guide/en/elasticsearch/client/community/current/index.html
- Spring Data JPA — https://spring.io/projects/spring-data-jpa
- Spring Data R2DBC — https://spring.io/projects/spring-data-r2dbc
- Exposed — https://github.com/JetBrains/Exposed
- Retrofit — https://square.github.io/retrofit/
- JUnit 5 User Guide — https://junit.org/junit5/docs/current/user-guide/
- Spek User Guide — https://www.spekframework.org/
- Spring Guide to Integration Tests — https://spring.io/guides/gs/testing-web/
- Reactive Manifesto — https://www.reactivemanifesto.org/
Originally published at http://docs.google.com.