Adopting Kotlin for Bootiful Microservices

Aju George
Expedia Group Technology
10 min readNov 15, 2018

The Beginning

A new project is always an exciting thing for any software developer.

At the Expedia Group, we are free to choose any language or technology that is more suitable for the task rather than being mandated by one language.

When my team started the journey into a new project, our first goal was to identify what technology stack we were going to use. This was a very exciting time for us and we got the chance to explore cutting-edge technologies.

Our team’s requirements for the new tech stack were:

  • A JVM based stack in the backend for easy integration with other libraries within Expedia Group.
  • A well-documented language or framework with a strong and growing developer community.
  • Time to market (TTM) factor, wherein the learning curve should not affect the delivery of the product.
  • Expertise within the team for setting up and guiding other team members with best practices for that language.
  • Development and maintainability of the code to support dynamic features and continuous delivery.

K for Kotlin

After our analysis and proof of concept, the decision for our backend API language was to use Kotlin.

Kotlin — developed by JetBrains, is a statically typed programming language, running on the JVM.

Unlike other JVM based languages, Kotlin is an intuitive language which can guide the engineers towards an elegant solution.

The following are some reasons Kotlin was well suited for our new stack:

  • data Classes
  • Null Safety
  • Named Arguments and Default values
  • Extension Functions
  • Kotlin Kollections
  • Higher-Order Functions
  • Inline and reified type
  • Spring boot Kotlin DSL

data Classes

When building RESTful API there are many layers of data manipulation and transformation that are required before the response is sent to the client applications. This results in creating data models that we can send and share across different layers within the codebase.

This becomes very simpler with Kotlin.

In Java, for example, one would have to do something like this:

public class Employee {    private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}

Whereas in Kotlin, it is just a one-liner:

data class Employee(val name: String, val age: Int)

Since the variables are defined as val, this creates them as an immutable variable with an associated getter. It also defines the toString(), equals() and hashCode() for the Employee class implicitly.

Null Safety

NullPointerExceptions is one of the common errors and most of the time this comes as a surprise in production. This is predominately due to the lack of null checks in the code.

Kotlin helps with this because, every variable is inherently not null by default, hence avoiding the need to explicitly check for nulls.

However, a variable can be marked as nullable by adding a (question mark) ? to the declaration.

For example:

data class Address(val line1: String, val line2: String?, val suburb: String)
data class Employee(val name: String, val age: Int, val address: Address?)

Here the variable line2 in the Address class and address in the Employee class are nullable variables.

If we want to access the variable line2 the Kotlin compiler will force us to use either ?. operator which is a safe operator for getting the value OR !! operator which basically tells the compiler that the value may not be null and the value is null a KotlinNullPointerException can be thrown.

val employee = Employee(name = "John", age = 32, address = Address(line2 = "6 Lala St", suburb = "Sydney"))employee?.address?.line2 // returns the value of line2 if address is not null or else it will return null.

One of the main pain point developers used to feel about Kotlin is the lack of support for ternary operator. But in reality, most of those cases can be handled by using the Elvis operator which is ?:

For example:

In Java, if you want to return the line2 of address that is not null and otherwise defaults to Undefined, we use the ternary operator:

String addressLine2 = (address.line2 != null) ? address.line2 : "Undefined";

In Kotlin, the same can be written using the Elvis operator:

val addressLine2 = address?.line2 ?: "Undefined"

In case an exception needs to be thrown, it can be done with:

val addressLine2 = address?.line2 ?: throw RuntimeException("Address line2 is not defined.")

Named arguments and Default values

This feature becomes very useful when writing unit tests with the test helpers.

For example: suppose we have the following scenario in the test case.

val q1 = Query("query1", listOf("a", "b", "c"), mapOf("a" to "abc", "b" to "bbc"), 10)
val q2 = Query("query2", listOf("a", "b", "c"), mapOf("a" to "abc", "b" to "bbc"), 20)
val q3 = Query("query2", listOf("a", "b", "c"), mapOf("a" to "abc", "b" to "bbc"), 30)
val processedQueries = queryProcessor.process(listOf(q1, q2, q3))

Here the difference between q1, q2 and q3 is with the name and the limit they are passing while creating the query objects. Therefore, instead of declaring the query repeatedly for those changes in the arguments, a helper function can be created as shown below that will set some default values for each of the arguments and implement the test function to pass only the values that are changing.

fun createQuery(
name: String = "query1",
criteriaNames: List<String> = listOf("a, "b, "c"),
params: Map<String, String> = mapOf("a" to "abc", "b" to "bbc"),
limit: Int = 10
) = Query(name, criteriaNames, params, limit)
val q1 = createQuery()
val q2 = createQuery(name = "query2", limit = 20)
val q3 = createQuery(name = "query3", limit = 30)
val processedQueries = queryProcessor.process(listOf(q1, q2, q3))

The usage of named parameters in the calling function helps with changing the definition of the class without breaking the tests that are not associated with the change.

Extension Functions

This is another cool and powerful feature in Kotlin, where an extension function for any class can be written. This becomes very useful when maintaining the functional style of programming in our code.

Suppose we have a string and we want to convert that string from any case to camel case. One way to do is write a function as:

fun toCamelCase(val str: String): String {
// logic goes here
}
val name = "someName"
val newName = toCamelCase(name)

Or we can write an extended function as:

fun String.toCamelCase(): String {
// logic goes here
}
val newName = name.toCamelCase()

This extension function will then be available to any string and can be imported and used by chaining the function calls while keeping the functional style.

Kotlin Kollections

The Collections.kt in the kotlin-stdlib has many extended functions to simplify boilerplate code we have to write while transforming values. This will keep the code clean and easy to read.

Assuming you are transforming a Map<String, String> to Map<String, Object> that converts a string value to list if it is contained within []

For example:

A map that contains <”A1", “[AA,BB,CC]”> should be converted to <”A1", [“AA”, “BB”, “CC”]>

whereas this map <”A2", “abc”> will be converted to <”A2", “abc”>, which is the same value itself.

The Java code using streams would be:

Map<String, Object> transformedMap = someMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> transformValue(e.getValue())));
private Object transformIfList(String value) {
return (value.matches("\\[.*]")) ?
Arrays.stream(value.substring(1, value.length() - 1).split(",")).collect(Collectors.toList()) : value;
}

Equivalent Kotlin code would be:

val transformedMap = someMap.entries.associateBy({ it.key }, { it.value.transformIfList() })fun String.transformIfList(): Any = if (this.contains(Regex("\\[*]"))) this.substring(1, this.length - 1).split(",") else this

The solution can be achieved by the stream manipulations in both languages and both are almost the same with the number of lines of code. However, it is not about the amount of code saved, it is about the fact that the Kotlin code is more intuitive and easier to understand because of the wrapper extension functions, such as associateBy{} around the normal Java collections.

Readability gets better when, for example, functions needs to be done directly on the list of values, such as sumBy{}, distinctBy{}, groupBy{} etc.

Another example is if you want to group a list of Person by age, who has the age greater than 10 and has a distinct name, it is as simple as writing:

persons.filter { it.age > 10 }.distinctBy { it.name }.groupBy { it.age }

Higher-Order Functions

Lambdas or higher order functions are not a new concept in modern day programming languages. A big advantage with Kotlin is the ease of usage and again the readability of code.

In Kotlin, if the last argument is a higher order function, the function value can be passed within the curly braces.

For example: suppose a function is defined in an InstrumentationService.kt class.

fun <R, O> instrument(name: String, classToInstrument: Class<R>, operation: () -> O): O {
// do the operation() and perform instrumentation for classToInstrument.
}

The calling function will be:

return instrumentationService.instrument("BasicOperation", ApiController::class.java) {
operationService.performBasicOperation()
}

Kotlin also supports function type with receiver, meaning code can leverage the extension function at the function variable level.

Assume for the above example, that the service requires instrumentation derived out of a common service called OperatorService.

Then a function can be defined as:

fun <R, O> instrument(name: String, classToInstrument: Class<R>, operation: OperatorService.() -> O): O {
// do the operation() and perform instrumentation for classToInstrument.
}

While calling that function the usage of operationService. can be avoided as shown below:

return instrumentationService.instrument("BasicOperation", ApiController::class.java) {
performBasicOperation()
}

Inline and reified type

The inline functions are mainly used for the higher order functions when to avoid the runtime overhead of allocating the memory for function objects and classes. But there is another use case of inline function when it can be used in conjunction with a reified type parameter when access to a type passed as the parameter is required.

The above example of the higher-order function which takes the class to instrument an argument is a bit harder to read and can be improved by using the inline and reified type as follows:

inline fun <reified R> instrument(name: String, operation: OperatorService.() -> O): O {
// do the operation() and perform instrumentation for R::class.java.
}

The calling function will be:

return instrumentationService.instrument<ApiController>("BasicOperation") {
performBasicOperation()
}

Bootiful Kotlin DSL

Since early 2017 Spring Framework 5.0 officially support Kotlin. The key building block for the support is Kotlin extensions. As Spring Boot 2.x uses Spring Framework 5.0, we can leverage the support for Kotlin DSL, that can then be used for declaring beans and routes in a more functional way.

A simple example on how to use the Kotlin beans DSL while initialising during the application startup would be:

Application.kt

@SpringBootApplication
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args) {
addInitializers(beans())
}
}

Beans.kt

fun beans() = beans {
bean("restTemplate") {
ref<RestTemplateBuilder>()
.setConnectTimeout(5000)
.setReadTimeout(60000)
.build()!!
}
bean<RestServiceHelper>()
}

Similarly, to use the reactive Spring WebFlux there is the more declarative way of defining the routes and then link up with their handlers. The Kotlin routing DSL is the functional way for writing clean and idiomatic Kotlin code.

Conclusion

Over 6 months of development work as a team, we feel we have made the right decision for the following reasons:

  • As a team, we were able to ramp up the basics of Kotlin as it had the best of all the flavours from different languages.
  • Code written in Kotlin is compact and concise with a lesser number of lines of code which means a lesser number of bugs.
  • Kotlin is an intuitive language and implicitly brings in clean procedural and functional code which is easy to read and debug.
  • Fully interoperable with Java which means in case we want to mix any Java code in Kotlin or vice-versa it is very easy to do.
  • Simple and easy to write and maintain code, especially complex collection manipulations, and thereby have faster development cycles.

Useful Materials

Kotlin Koans

If you are new to Kotlin and want to learn the basic syntax by doing some series of exercises then Kotlin Koans is the place you have to be in. It really helps to kick start with the language by solving each problem and making it pass the failing tests.

Kotlin in Action

I would highly recommend this book Kotlin in Action which is written by Dmitry Jemerov and Svetlana Isakova, the creators themselves. It explains the high-level concepts and covers all the basics of Kotlin language.

Introduction to Kotlin Programming

If you are into watching video tutorials, I would recommend this Introduction to Kotlin Programming in the Safari Book Online to start with as it covers all the basics and then can move on to more advanced concepts in Advanced Kotlin Programming.

Bootiful Kotlin

I would also recommend this talk from the Kotlin Conf 2018 which shows some of the powerful and cool features of Kotlin DSL with the new Spring Boot 2.

Kotlin is a good choice for new micro-services wherein the team would still like to use a JVM based language, but do not want to do much of the boilerplate code Java is offering. Kotlin is been widely used as a language for the Android App development, however, based on our experience we would recommend to use it for backend web services as well.

Happy coding in Kotlin, because Kotlin is fun.

--

--