Kotlin for Backend
Hi!
linkedin.com/in/cosminstefan
cosmin@greenerpastures.ro
Goal for today?
We've all heard
of Kotlin
But why?
A quick recap
Very powerful
General Purpose
Easy to learn
Great tooling (by JetBrains)
JVM compatible
Open Source (Apache 2.0)
Platform targeting
Android (Kotlin/JVM)
Server (Kotlin/JVM)
iOS (Kotlin/Native)
Browser (Kotlin/JS)
Fine... Kotlin...
What now?
Quite a few options!
Many Server-side Frameworks
Spring
Vert.x
Spark
Http4K
Ktor
Ktor
Framework for building asynchronous servers and clients in connected systems using Kotlin
Written in pure Kotlin
Jetbrain's official framework
Strong backers:
Lightweight & unopinionated
Very fast boot & routing
Performance:
Backed by Coroutines
Suspending API via NIO
Asynchronous I/O:
Servlet containers (Tomcat, Jetty)
Standalone (Docker, Fat JAR)
Hosting (AppEngine, Heroku)
Unopinionated deploy:
Authentication
CORS
Serialization
Powerful features:
Sessions
Templating
Compression
Static content
HSTS
Web Sockets
And even more features:
Auto-reload
Metrics
Logging
And many more ...
Project Setup
Ktor
start.ktor.io / IntelliJ Plugin
Base project structure
Bootstrapping:
start.ktor.io
Base project
Gradle Build Setup
Pre-Configured dependencies
Ready to run
fun main(args: Array<String>): Unit = io.ktor.server.cio.EngineMain.main(args)
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) { ... }
install(Compression) { ... }
install(AutoHeadResponse)
install(CallLogging) { ... }
install(CORS) { ... }
routing {
get("/") {
call.respondText("Hello World!", contentType = ContentType.Text.Plain)
}
get("/html-dsl") {
call.respondHtml {
body {
h1 { +"HTML" }
ul {
for (n in 1..10) {
li { +"$n" }
}
}
}
}
}
get("/json/gson") {
call.respond(mapOf("Hello" to "World"))
}
}
}
Easy-win Features
Ktor
Defaults to SLF4J & Logback
Auto Call Result Logging
Logging:
fun Application.module(testing: Boolean = false) {
log.debug("App has initialized at: {}", Instant.now())
install(CallLogging) {
level = Level.INFO
filter { call -> call.request.path().startsWith("/") }
}
...
}
Fast feedback loop cycle
Simple setup: watch list
Autoreload:
# In application.conf
ktor {
deployment {
port = 8080
watch = [ main, module1, module2 ]
}
…
}
Then just build the app!
Compress outgoing content
gzip, deflate, identity
Compression:
fun Application.module(testing: Boolean = false) {
install(Compression) {
gzip {
priority = 1.0
}
deflate {
priority = 10.0
minimumSize(1024) // condition
}
}
...
}
Automatic content conversion
Based on Content-Type and Accept headers
Content negotiation:
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
register(ContentType.Application.Json, JacksonConverter())
}
...
routing {
get("/data") {
call.respond(MyDataClass("Hello", "World"))
}
post("/data") {
val myRequest = call.receive<MyRequestClass>()
}
}
}
Automatic handling
Opens API for JavaScript calls
CORS Support:
fun Application.module(testing: Boolean = false) {
install(CORS) {
method(HttpMethod.Patch)
method(HttpMethod.Options)
header(HttpHeaders.XForwardedProto)
host("example.org")
host("example.com", subDomains = listOf("www"))
}
...
}
Build HTML responses
Freemarker, Mustache, Thymeleaf, Veolcity, KotlinX
Templating:
fun Application.module(testing: Boolean = false) {
install(Mustache) {
mustacheFactory = DefaultMustacheFactory("templates")
}
routing {
get("/html-mustache") {
val todo = Todo("Prepare presentation")
call.respond(MustacheContent("todos.hbs", mapOf("todo" to todo)))
}
get("/html-dsl") {
call.respondHtml {
body {
h1 { +"HTML" }
ul {
for (n in 1..10) {
li { +"$n" }
}
}
}
}
}
...
}
}
Routing
Ktor
Support for simplifying and structuring
request handling
Valid paths for requests
Functions that process the requests
Main roles:
fun Application.module(testing: Boolean = false) {
routing {
get("/") {
call.respondText("Hello World!")
}
route("/api/todos") {
// Final path: /apis/todos
get {
val todosList: List<Todo> = dataRepository.loadTodos()
call.respond(todosList)
}
// Final path: /apis/todos/items
post("/items") {
val item = call.receive<Todo>()
dataRepository.addTodo(item)
}
}
}
}
route defines a base path for children
APIs can be separated
Keeping a clean structure:
fun Routing.todosApi() {
val dataRepository: DataRepository = ... // inject repository
route("/todos") {
// Resolves to /todos
get {
val todosList: List<Todo> = dataRepository.loadTodos()
call.respond(todosList)
}
route("/items") {
// Resolves to /todos/items
post {
val item = call.receive<Todo>()
dataRepository.addTodo(item)
}
// Resolves to /todos/items/{id}
delete("/{id}") {
val itemId = call.parameters["id"]
dataRepository.removeTodo(itemId)
}
}
}
}
fun Application.module() {
routing {
route("/api/v1") {
// Install TODOs API module (Final path: /api/v1/todos)
todosApi()
// Install User Profile API module (Final path: /api/v1/profile)
profileApi()
}
// Install main HTML Web app module
webApp()
}
}
Dependency Injection
Koin
A pragmatic lightweight dependency injection framework for Kotlin
No proxy
No code generation
No reflection
Performance:
Define modules
Lazy inject at
consumption location
Main principles:
class AuthenticationService {
...
}
class DataRepository(val authenticationService: AuthenticationService) {
...
}
val mainModule = module {
single { AuthenticationService() }
single { DataRepository(get()) }
}
fun Application.module(testing: Boolean = false) {
install(Koin) {
// Use SLF4J Koin Logger
slf4jLogger()
// Declare used modules
modules(mainModule)
}
...
routing {
// Lazy inject DataRepository
val dataRepository: DataRepository by inject()
get("/data") {
call.respond(dataRepository.loadData())
}
...
}
}
SQL Framework
Exposed
A lightweight SQL library for Kotlin, by Jetbrains
Typesafe SQL wrapping DSL
Lightweight data access objects (DAOs)
Layers of database access:
Typesafe
Full querying support
Comprehensive features:
PostgreSQL, MySQL, H2
MariaDB, Oracle, SQLite, SQL Server
Currently supported dialects:
object Users : Table("users") {
val id = integer("id").autoIncrement().primaryKey()
val name = varchar("name", 128)
}
object Todos : Table("todos") {
val id = integer("id").autoIncrement().primaryKey()
val description = varchar("description", 256)
val reminderAt = datetime("reminder_at")
val userId = integer("user_id").references(Users.id, ReferenceOption.CASCADE)
}
class TodosRepository {
init {
Database.connect("jdbc:h2:mem:todos", driver = "org.h2.Driver")
transaction {
addLogger(Slf4jSqlDebugLogger)
SchemaUtils.create(Users, Todos)
}
}
...
}
class TodosRepository {
...
fun loadTodos(): List<String> = transaction {
// SELECT * FROM TODOS WHERE TODOS.USER_ID = {userId}
Todos.select { Todos.userId eq userId }.map { it[Todos.description] }
// SELECT TODOS.DESCRIPTION FROM TODOS
Todos.slice(Todos.description).selectAll().map { it[Todos.description] }
}
}
class TodosRepository {
...
fun addTodo(userId: Int, description: String): Int = transaction {
// INSERT INTO TODOS (DESCRIPTION, REMINDER_AT, USER_ID) VALUES ('Do stuff', NULL, 1)
val todoId = Todos.insert {
it[Todos.description] = "Do stuff"
it[Todos.userId] = userId
} get Todos.id
return@transaction todoId
}
}
Deploying to the cloud
AppEngine
Project on Google Cloud
gcloud cli
Pre-requisites:
buildscript {
ext.appengine_version = '1.9.60'
ext.appengine_plugin_version = '1.3.4'
repositories {
jcenter()
}
dependencies {
...
classpath "com.google.cloud.tools:appengine-gradle-plugin:$appengine_plugin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'war'
apply plugin: 'com.google.cloud.tools.appengine'
sourceSets {
main.kotlin.srcDirs = [ 'src/main/kotlin' ]
}
...
dependencies {
....
providedCompile "com.google.appengine:appengine:$appengine_version"
}
task run(dependsOn: appengineRun)
# Run the app locally, on http://localhost:8080
./gradlew :google-appengine-standard:appengineRun
# Setup the project on Google Cloud
gcloud app create
# Deploy the application to the cloud, to https://demo-ktor.appspot.com
gradle :google-appengine-standard:appengineDeploy
What to keep in mind?
Thank you!
Kotlin for Backend
By Cosmin Stefan
Kotlin for Backend
Kotlin is a great fit for developing server-side applications, allowing you to write concise and expressive code while maintaining full compatibility with existing Java-based technology stacks and a smooth learning curve. We’ll be going over the process of building an entire backend API using Kotlin, with the right tools, from dependency injection to routing to database access.
- 810