July 22, 2021
Streamlining Server-Side App Development with Kotlin
Authors: Rares Vlasceanu, Florentin Simion, and Jenny Medeiros.
This is the first of a three-part series in which we re-introduce developers to Kotlin as an alternative for building server-side applications. In this post, we focus on the advantages of Kotlin and why we prefer it over Java for our own server-side app development in Adobe Experience Platform.
With the rise of mobile and modern web applications, backends have evolved from monolithic web containers to a distributed mesh of data-driven applications. Web servers can no longer be limited to simply printing web pages, but must also receive, store and transform data destined to reach user devices in record time.
Traditionally, Java was the language of choice for developers building server-side applications targeting the Java Virtual Machine (JVM).
But with limited features for today’s modern requirements, Java often results in cumbersome developer experience and a painfully slow time-to-value for many applications.
Kotlin is an open-source programming language that offers many of today’s expected features and solves dozens of issues that Java suffers from. With a short learning curve, Kotlin quickly allows developers to write concise and expressive code both in new and existing applications thanks to its full interoperability with Java stacks.
While Kotlin is a top choice for developing Android applications, it has long been overlooked for server-side application development. In this post, we highlight Kotlin’s potential beyond mobile and why we switched from Java to Kotlin for increased productivity and a vastly better developer experience.
Our shift from Java to Kotlin
In Adobe Experience Platform, most of the backend applications are built in Java. The second most used JVM language is Scala, which is mostly employed in data processing pipelines that run through many terabytes of information daily. These pipelines are also built on top of big data frameworks that have an affinity for this language.
Since Adobe Experience Platform revolves around providing actionable customer insights in real-time, we are constantly looking to improve the way we develop those real-time services. Originally, we simply defaulted to Java, but as we ran into similar obstacles time after time we began to hunt for alternative programming languages.
Eventually, we discovered Kotlin and fell for its concise and easily readable syntax, asynchronous capabilities, and seamless interoperability with Java. And so we slowly began to adopt Kotlin in our projects. At first, we tentatively used it in conjunction with Java, then gradually worked our way up to building entire business-critical applications solely on Kotlin.
Once we had grown comfortable with Kotlin and were fairly confident in its capabilities, we introduced it to the larger developer community at Adobe. Now, Kotlin is internally acknowledged as a reliable alternative to Java for building real-time services.
Below we will briefly describe the major differences between Kotlin and Java that convinced us to shift to the newer language — along with the challenges we bumped into on the way.
How Kotlin improves server-side application development
While Java is generally appreciated for offering performant, battle-tested features, we have struggled with a few key shortcomings that Kotlin thankfully compensates for:
Functional programming
While lambda support was introduced in Java 8, its functional programming leaves much to be desired. With no functions as first-class citizens and the need to represent them through functional interfaces, it is difficult to write higher-order functions and adopt other functional principles.
In contrast, Kotlin functions are first-class, which means they are treated like any other variable along with support for features including lambdas and anonymous functions, functions with the receiver, among others. Additionally, with Kotlin you can naturally have programs written in both imperative and functional style, which makes the transition from one to another a much smoother journey.
Null
A well-known runtime error is the infamous NullPointerException — the source of many bugs and subsequent frustration for developers and application users alike. Java’s best practices recommend adding explicit null-checks as a solution, but this comes at the cost of code readability.
In contrast, Kotlin pushes nullability into the type system, giving you distinct representations for types that can be assigned to null and types that can’t. This way the compiler carries out the proper checks rather than wait for the developer to realize their mistake.
Although this feature has its limits. NullPointerException might still happen in Java code that is called from Kotlin, or when using a library that manipulates object internals at runtime, through Java reflection APIs.
Support for value objects
This was one of the biggest pain points while developing in Java and pushed us towards code generators, such as Immutables or Project Lombok, which provide simple and consistent value objects. For all their benefits, these open-source processors also gave us grief with extensive debugging times and temperamental functionality with the integrated development environment (IDE).
With Kotlin we now have data classes, which remove the boilerplate of manually implementing the usual functions (e.g. equals, hashCode, toString) and also fit a value object declaration on a single line, leaving the compiler to generate that code for us.
Furthermore, the traditional builder that generally accompanied these value objects to ensure immutability is no longer needed. This is because Kotlin has default and named function arguments, and reference immutability is assured through the val keyword.
Type system
Java provides boxed and unboxed types for primitive values, putting the “burden” on the developer to choose between them, whereas Kotlin only has one type and leaves the compiler to choose the appropriate representation. Also, when it comes to generics, Java is limited to use-site variance, which must be handled for every specific type and makes the code overly complex.
Any developer coming from Java will immediately notice Kotlin’s particular type system, which supports expressing nullable and non-nullable types and also includes features such as mutable and immutable local variables and fields, better support for generics with declaration-site variance, and strong type inference. With type inference, in particular, developers don’t have to declare the variable type, which makes the code refreshingly concise and readable.
Asynchronous code
Virtually all server-side applications nowadays are I/O heavy since they rely on external services including queues, various types of databases, blob storage systems, or other services that you call over the network. Workloads like these can quickly burn through resources and heavily limit the application’s throughput unless running asynchronously.
While Java does provide the ability to create multiple threads in the background to help distribute the work, it’s not easy to write low-level, multi-threaded code that does not suffer from race conditions.
As a high-level alternative, we tried many Java reactive libraries with some success, but also with a great deal of complexity that wasn’t sustainable for our large-scale applications. With Kotlin, we can write code in “direct style” that reads almost like blocking, imperative code. Under the hood, Kotlin delegates most of the asynchronous operations’ complexity to its compiler and runs everything in coroutines.
Coroutines are essentially “light-weight threads” that use very little memory and enable long-running tasks through clear, concise, non-blocking asynchronous code. The flexibility and scalability of coroutines align with Kotlin’s philosophy of simplicity, while still offering very important features, such as structured concurrency and asynchronous flows.
Implementation challenges
One unexpected challenge surfaced when we introduced Scala into the Kotlin-Java equation. While Kotlin’s libraries are fully interoperable with Java projects, integrating the same Kotlin libraries in a Scala project resulted in a broken build and (ironically) a NullPointerException.
We later determined that the cause was a bug in the Scala 2.11 compiler, which failed to properly interpret the legitimate bytecode produced by Kotlin’s compiler. This goes to show that there are limitations to Kotlin’s interoperability — albeit not with Java.
Another ongoing challenge, that is not at all unique to Kotlin, is convincing developers to embrace a new language. There is certainly a ramp-up period for anybody moving from another JVM language to Kotlin or integrating Kotlin into an existing project. Although Kotlin is arguably one of the easiest JVM languages to learn and having experience with functional programming can make the transition easier.
Even so, developers should always assess the entire ecosystem of tools, frameworks, and libraries before choosing the most suitable language for their project. We will argue, however, that Kotlin is a strong option for building scalable web applications capable of handling large workloads in real-time. This preference is also reflected in the larger ecosystem, with first-class support from major web frameworks like Spring Boot, Vert.x, and Quarkus.
What’s next for Kotlin at Adobe
Currently, for Adobe Experience Platform we have three projects in flight with Kotlin: two are developed in Kotlin from scratch and the other is a legacy project originally written in Java that we are switching from blocking to non-blocking I/O with the help of Kotlin and its coroutines.
Other teams across Adobe are gradually picking up Kotlin for its many benefits and capacity to significantly speed up development. Although convincing different teams around the world to embrace Kotlin remains a challenge since it is better known in the mobile world and generally perceived as unsuited for server-side applications.
Nevertheless, we enthusiastically encourage developers to try Kotlin as it makes a genuine difference in productivity. Furthermore, we can say from experience that the cost and risk of adopting Kotlin will pay off in the long run.
As for us, we will continue using Kotlin to develop server-side applications for Adobe Experience Platform with increased ease and efficiency. Our next post in this series will describe in detail one of the many ways we are leveraging Kotlin: building elegant domain-specific languages (DSLs).
Follow the Adobe Tech Blog for more customer and developer stories and resources, and check out Adobe Developers on Twitter for the latest news and developer products. Sign up here for future Adobe Experience Platform Meetups. For exclusive posts on Adobe Experience Platform, follow Jaemi Bremner.
Additional resources
- Kotlin: https://kotlinlang.org/
- Scala: https://www.scala-lang.org/
- Adobe Experience Platform: https://www.adobe.com/experience-platform.html
- Kotlin Structured Concurrency: https://kotlinlang.org/docs/reference/coroutines/basics.html#structured-concurrency
- Kotlin Asynchronous Flows: https://kotlinlang.org/docs/reference/coroutines/flow.html
- Kotlin generics compared to Java generics: https://kotlinexpertise.com/kotlin-generics-and-variance-vs-java/
- Getting started with Kotlin: https://play.kotlinlang.org/koans/overview
No comments:
Post a Comment