Amazon Web Services
Amazon Web Services (AWS) is the world’s most comprehensive and broadly adopted cloud platform, offering over 200 fully featured services including compute, storage, security, networking, mobile, developer tools, database and data lakes, analytics, artificial intelligence, machine learning, blockchain, IoT and robotics from dozens of data centers in Europe and worldwide. Millions of customers—including the fastest-growing startups, largest enterprises, and leading government agencies—are using AWS to lower costs, become more agile, and innovate faster. AWS supports customers in the Benelux from offices in Amsterdam, The Hague, Brussels and Luxembourg.
AI Services on AWS*
*No machine learning skills required to implement Artificial Intelligence services such as Image processing, personalization, chatbots, automated code reviews and many more
Adopting Kotlin at Amazon Prime Video for higher developer satisfaction and less code
By Varun Jewalikar & Marcos Arranz
Choosing a programming language for a new project is a tough decision with long-lasting effects. This involves considering how well the languages integrate with the team’s existing technology stack, how mature the languages are, and what learning curve is required.
In March 2020, Prime Video launched Prime Video profiles. This functionality lets Prime Video users access separate recommendations, season progress, and Watchlist, as these are based on individual profile activity. This new customer experience required the design and implementation of new micro services, and the team decided to use Kotlin (rather than Java) to develop these micro services. This decision also motivated the development of the AWSSSMChaosRunner library (an open source library for chaos engineering on Amazon Web Services [AWS]) in Kotlin.
In this article, we start with a brief overview of Prime Video’s software development culture, followed by a deep dive into the team’s adoption of Kotlin and its impact.
Prime Video’s software development is organised around many different two-pizza teams—small teams that build, maintain, and own components of the call-stack (micro services). This approach enables fast delivery while maintaining a high degree of independence. The result is a culture of innovation, where software developers are empowered and encouraged to make informed choices about the technologies they build with. Choosing a programming language is one such choice, and this post details the experience of one specific team in choosing and adopting Kotlin.
While designing new micro services, a Prime Video software development team decided to write these using Kotlin. The choice centred around Kotlin features which the team believed would lead to less code, fast delivery, and increased application throughput.
A phased approach
Although the team had previous experience with Kotlin, adopting a new language in a high-traffic (think hundreds of thousands of requests per second) and low-latency production environment can be daunting! Thus, a phased approach was chosen.
The first phase consisted of implementing the tests of an existing Java package in Kotlin. Kotlin’s Java interoperability allows the same package to contain code in both languages. This phase was successful, and the team soon started experimenting more with Kotlin. The most-used Kotlin features included:
- Null Safety: A common runtime error in Java is the NullPointerException, caused by any type being assignable to null. Kotlin, on the contrary, includes the concept of null directly into the type system. That means there are nullable and non-nullable types, which enable compile time checks to prevent this error.
- Data classes: A common pattern in Java is the Plain Old Java Object (POJO), which is used to encapsulate data. It introduces a lot of boilerplate, like getters and setters, toString() method, equalTo() method, hashCode() method, and builder methods. Kotlin data classes make this quite concise (one line of code).
- First-class functions: Kotlin functions are first-class, which means they can be stored in variables and data structures, passed as arguments to, and returned from other higher-order functions. You can operate with functions in any way that is possible for other non-function values.
- Extension functions: Kotlin classes can be extended with new functionality without the need to inherit them. For example, the String class, can be extended with a user function that can be invoked from any String object.
At this stage, the team was more confident using Kotlin and decided to proceed to the next phase and develop new micro services using it.
During the second phase, some changes in tooling were necessary with the team adopting Detekt (Static code analysis for Kotlin), ktLint (a Kotlin linter with built-in formatter), MockK(a mocking library), and Koin (dependency injectors written in pure Kotlin).
Kotlin in distributed systems
A key requirement for the new micro services (being designed to support Prime Video profiles) was supporting thousands of requests per second per host while maintaining high availability. The requests would be I/O heavy, and asynchronous (non-blocking) programming would improve application throughput by increasing parallelisation, and reducing I/O wait-times from blocking subsequent execution.
This can be done in Java using Futures, but they quickly become complex if there are multiple dependent calls, and all failure scenarios need to be evaluated. Kotlin coroutines improve this process. They can be thought of as lightweight threads and are more readable.
Coroutines are similar to lightweight threads, but ultimately they are executed in a thread. We learned this the hard way. Coroutine scopes let you specify the Dispatcher where it will be run, and, if none is specified, it will run on the Dispatcher of the parent scope. If none is selected in the root scope, it will run by default in the default Dispatcher, which is backed by a number of threads equal to the number of CPU cores.
Normally, this would not be an issue, but if any of the calls in the coroutine are blocking, the underlying thread won’t be released while waiting, thereby quickly consuming all available threads. The obvious way to solve this issue is to use a non-blocking client; if that is not possible, then you should specify Dispatcher.IO or configure a purpose-fit Dispatcher.
These kind of bugs are hard to spot without representative load, which in many cases is only found in the production environment. The AWSSSMChaosRunner library was used to validate the fix for this issue by simulating a latency increase that re-created the behaviour.
We conducted an anonymous satisfaction survey to gauge the team’s happiness with adopting and using Kotlin. The Kotlin team satisfaction survey consisted of eight Amazon employees. The results were positive, as seen in the figure on the right.
The Kotlin adoption has been a positive experience with long-lasting benefits, such as code reduction, increased application throughput, and higher developer satisfaction. The team’s Kotlin code base is more readable (compared to a similar Java code base), while also making it easier to work with NullPointer exceptions and leveraging a more robust type system. For this team of Java developers, the adoption of Kotlin was smooth, and the main lessons learned centered around the increased use of Kotlin coroutines.
The team’s Kotlin code base is more concise (compared to a similar Java code bases), which is important because less code leaves less room for bugs. A like-for-like comparison has not been done, but the observed reductions are similar to what is quoted in the Kotlin FAQ—approximately 40 percent reduction in lines of code.
At re:Invent 2021, we announced the Developer Preview of the SDK for Kotlin, which provides Kotlin APIs for all your favourite AWS services. You can check out the GitHub repository , view the getting started guide, provide feedback for the SDK, and view the public roadmap.
Amazon Prime Video profiles – https://www.primevideo.com/help/ref=atv_nb_lcl_en_US?_encoding=UTF8&nodeId=GD8VJD2EDJ2GSNEC
Amazon Two-Pizza teams – https://docs.aws.amazon.com/whitepapers/latest/introduction-devops-aws/two-pizza-teams.html
Detekt (Static code analysis for Kotlin),
ktLint (a Kotlin linter with built-in formatter),
MockK(a mocking library),
Koin (dependency injectors written in pure Kotlin) – https://insert-koin.io/