Kotlin on the Backend at Rocket Travel
--
Kotlin has gotten a lot of attention since JetBrains and Google announced first party support for the language on Android back in May. Full interoperability with Java has made it a welcome alternative for developers looking for a more modern, succinct language to use while building mobile applications.
But it isn’t just for Android. In fact, you can use Kotlin anywhere you’re already using Java, which makes it a great option for server-side
development. At Rocket, we’ve been using the language in production for about a year, and it’s offered a welcome boost in productivity while keeping
with the performance and third-party library support you’d expect from working on the JVM.
Managing SSO Sessions with Kotlin and Spring Boot
At Rocket Travel we interact a lot with various airline Loyalty Program APIs. For one of our products, we decided to extract SSO session management into its own service. Kotlin, having recently gone 1.0.0 at the time, and promising less verbosity than Java while maintaining speed and performance, seemed like a great fit.
Spring Boot was also an obvious choice, as it allows you to spin up micro-services quickly, and integrates with Kotlin out of the box. Getting a minimal web application up and running couldn’t be more simple:
@SpringBootApplication
class Application
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
Kotlin integrates well with standard JPA annotations, allowing for easy conversion of your existing Entities to Kotlin:
@Entity
@Table(name = "application")
class ClientApplication(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
val id: Long,
val applicationName: String
)
Lastly, if your application uses Jackson for handling JSON requests and responses, an additional dependency is all that’s required for allowing the standard annotations to work with Kotlin data classes:
@Entity
@Table(name = "session")
data class Session(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
val _pk: Long,
val id: UUID = ClientApplicationConstants.EMPTY_UUID,
var ttl: Long = ClientApplicationConstants.SESSION_TTL
val dateEntered: Timestamp = Timestamp(DateTime().millis)
val userId: String,
val portal: String,
val partner: String,
@Enumerated(EnumType.STRING)
var status: SessionStatus = SessionStatus.PENDING,
@Embedded
var opaqueAccessToken: OpaqueAccessToken? = null
)
The JetBrains team made interoperability a priority when designing the language, and it shows given the ease of consuming well known Java libraries and frameworks.
If you’re interested in introducing Kotlin into your project file-by-file, JetBrains has you covered with their Mixing Java and Kotlin tutorial.
Modularized API Client JARs
Onboarding a new loyalty partner at Rocket requires developing a client to interact with their Web APIs. Naturally, establishing a common set of interfaces and developing to them in separate projects per partner made sense.
This architecture isn’t a problem with Kotlin as everything compiles to JVM bytecode and can be distributed and consumed as JARs. This allowed us to keep logic common to all partners within our main SSO Session management application, while calling partner specific functions from the libraries as needed.
While this approach isn’t specific to Kotlin, it’s important to note that the language didn’t hinder how we wanted to design our system.
Packaging Kotlin Code
Creating the individual client libraries and enabling Kotlin was straight forward. It’s was really no different from a Gradle perspective than it would’ve been in Java, save for the additional dependency and plugin needed in the build.gradle
file:
buildscript {
ext.kotlin_version = '1.1.60'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}apply plugin: 'kotlin'dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib"
}
Running ./gradlew jar
will still create an executable JAR file when you’ve got the kotlin
Gradle plugin enabled. Publishing this jar to something like a private Maven repository or Artifactory is no different either.
We also distribute a client for our SSO Session management application, which is consumed by both Java and Groovy services. The “Interoperability” that Kotlin offers is moot once the code is packaged and distributed. Once a Kotlin library is compiled into a JAR, it can be considered a Java library (save for some metadata that lets you treat it as Kotlin code provided you have the Kotlin plugin enabled and stdlib as a dependency). In our experience, anything that can consume a Java Library, like Groovy, can consume Kotlin JARs as well.
Costs of Kotlin JARs
So, what’s the cost of writing your libraries in Kotlin?
Writing and distributing the code in Kotlin results in < 1Mb difference in transitive dependencies. For most developers, these additions to your application are negligible.
It’s worth noting that the kotlin-stdlib-jre7
and kotlin-stdlib-jre8
libraries are optional, and only needed if you need access to JDK APIs added in Java 7 or Java 8.
Keeping Kotlin Dependencies Up to Date
As of this writing, Kotlin is currently at version 1.2. Mixing patch versions of Kotlin has so far been non-issue, but rapid version release means it’s easy to fall behind quickly. The approach we’ve taken is to update to the new patch versions when convenient, and don’t sweat it if multiple patches have been released since you last updated the library.
JetBrains have documented that patches will keep compatibility, and minor versions will guarantee backward compatibility as well. There are however no promises for compatibility between major versions.
Releases have been frequent between patch versions, at a pace of about 1–2 patches per month according to the release dates on the standard libraries maven repository. If you’re interested in keeping up with the Kotlin release cycle, JetBrains will usually post on their blog when a new version is released.
Documentation
Dokka, the official Kotlin documentation engine, has been great for adding JavaDocs to these client libraries. Adding Dokka to your project requires an additional buildscript dependency and applying the dokka
Gradle plugin.
buildscript {
dependencies {
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
}
}
Once added, it can be configured using the dokka
block in the build.gradle
file:
dokka {
moduleName = 'partner-api-client'
outputFormat = 'javadoc'
outputDirectory = "$buildDir/javadoc"
includes = ['dokka.md']
}
The above sample configuration takes the Gradle module partner-api-client
, outputs the documentation in javadoc
format to the build/javadoc
directory of the project, and includes an additional dokka.md
file at the root of directory. The additional file can be used (and named differently if you choose), to specify module and package documentation.
By specifying javadoc
format, Dokka generates Java formatted documentation despite the library being written in Kotlin. We were able to distribute and provide docs to other teams that may not have adopted the language into to their projects yet.
AWS Lambda and Cloudwatch Triggers as an Alternative to cron
When we onboarded our first loyalty program partner that used FTP and flat files for getting users their loyalty points, we needed a way to batch the submitted point allotments, package them into a file, and send that file off to the partner’s FTP Server. We were hesitant to introduce any sort of heavy scheduling framework like Quartz, a popular job scheduler for JVM languages.
We eventually settled on a serverless approach. Luckily, AWS Lambda already supports JARs, and the only addition to the SSO session management service was a set of endpoints that applications could use to submit, read, and update their points requests. These same endpoints could be used by new AWS Lambda functions to do all the heavy lifting.
Building the Lambda Function
Kotlin AWS Lambda functions (not to be confused with the language feature) aren’t really different from those written in Java. The project just needs the aws-lambda-java-core
and aws-lambda-java-events
libraries, and we need to make sure we’re publishing a “fat JAR” so all dependencies are included in the JAR itself.
dependencies {
compile "com.amazonaws:aws-lambda-java-core:1.1.0"
compile "com.amazonaws:aws-lambda-java-events:2.0"
}
jar {
from { configurations.compile.collect
{ it.isDirectory() ? it : zipTree(it) }
}
}
When we were ready to distribute the code, we packaged it using ./gradlew jar
and uploaded the artifact to S3. If you‘re interested in automating the deployment step, there are Gradle plugins that let you upload artifacts to S3 and update AWS Lambda Functions as well.
Setting up the Lambda Function and CloudWatch
After we had uploaded our jar to S3, and created the Lamda function, we needed to setup our trigger.
Luckily for timed batch jobs, AWS CloudWatch offers a way to schedule different Lambda Functions using a cron expression:
The above image shows a CloudWatch rule to trigger the test-fn
Lambda Function Monday through Friday at 1AM GMT. Those familiar with writing cron expressions will feel right at home, and AWS even provides you with the next 10 trigger dates for you to check your work.
Organizing Lambda Functions in Repositories
We’ve found the best way to organize and maintain the individual Lambda Functions is by separating them into repositories by business function and partner.
For example, we have a few separate Lambdas that run at different times to package and send off files for a partner to process, and another to process the reconciliation file that they return later in the day. While these processes are executed by different Lambda Functions (the entry point for each just being different handlers), the code for both of them lives in the same partner specific Git repository. If we wanted to do the same for other partners, our approach would be to make a new repository for each.
Testing with JUnit, Mockito-Kotlin, and Spek
JUnit and Mockito is a popular approach to testing in Java. This holds up well in Kotlin, with a few caveats. Classes are final by default in Kotlin, so you need to either: a) Mock only Interfaces or b) Add the mockito-inline
dependency in order to Mock final classes.
testCompile "org.mockito:mockito-inline:2.8.47"
Using Mockito
Once we had Mockito added to the project, it was time to get to work writing our tests.
Those familiar with Mockito will know when
is used pretty frequently in defining behavior for your Mock Objects. Those familiar with Kotlin will know that when
is a reserved word involved in control flow:
when (response.status) {
Status.SUCCESS -> print("Success")
Status.FAILURE -> print("Failure")
}
The vanilla Mockito solution is to surround when
with backticks when defining your mocks behavior. For example:
Mockito.`when`(foo.bar()).thenReturn("foobar")
This solution feels okay, but backticks in function calls don’t look great. So what other option is there for stubbing methods and properties?
Enter Mockito-Kotlin
Mockito-Kotlin is a unofficial, but actively maintained, library that provides Kotlin friendly wrappers for Mockito.
The `when`
from before can now be called a little differently without backticks:
whenever(foo.bar()).thenReturn("fooBar")
The library also provides a more Kotlin-like approach to creating mocks and stubbing their behavior:
val foo: Foo = mock {
on { it.bar() } doReturn "fooBar"
on { it.baz() } doReturn "fooBaz"
}
While the library itself is just syntactic sugar, using it and calling the Mockito code the “Kotlin way” makes things readable and concise. Describing mock behavior as functions felt like working with a new, modern, tool instead of just writing in a less verbose Java.
These syntax niceties aren’t just for stubbing either. I encourage anyone working with Mockito and Kotlin to take a look at the mockito-kotlin wiki for more examples.
Spek
If you’re interested in an alternative to JUnit centered around Behavior Driven Development, you may be interested in checking out Spek. The explicit definitions of Specifications are intended to add more clarity to the exact conditions of your system that you are trying to verify:
class FooSpek: Spek({
describe("A Foo") {
val foo = Foo()
it("should foobar when bar") {
val bar = foo.bar()
assertEquals("foobar", bar)
} it("should foobaz when baz") {
val baz = foo.baz()
assertEquals("foobaz", baz)
}
}
})
Spek itself was a welcome change in how we approached writing tests for some libraries. Be aware though if you’re using Spring, Spek may not play nicely with it’s TestContext.
While we have ventured into Spek for some projects, we’ve stuck with JUnit and Mockito / mockito-kotlin for most of our testing.
The Future of Kotlin at Rocket
We’ve been using Kotlin in production at Rocket Travel for a year. Whether it’s using it on a greenfield project and going 100% Kotlin, or introducing it slowly into your project one class at a time, it’s easy to start experimenting with how it fits into your current application. The focus on Interoperability let’s you ease into it, with only a couple added dependencies. You can even introduce Kotlin into only your tests if you’re nervous about putting Kotlin code into production!
So far we’ve written an entire Microservice, AWS Lambda Functions, and client libraries packaged and distributed as JARs. We’re continuing to experiment with Groovy interoperability, Spark Applications, Kotlin Build Scripts for Gradle and working with various open source libraries that the community has produced. We’re buying into the language and what it has offered us in terms of productivity and safety.
If you’re interested in working with Kotlin or just enjoy traveling, Rocket Travel is Hiring!