linkedin.com/in/cosminstefan
cosmin@greenerpastures.ro
Very powerful
General Purpose
Easy to learn
Great tooling (by JetBrains)
JVM compatible
Open Source (Apache 2.0)
Android (Kotlin/JVM)
Server (Kotlin/JVM)
iOS (Kotlin/Native)
Browser (Kotlin/JS)
Spring
Vert.x
Spark
Http4K
Ktor
Framework for building asynchronous servers and clients in connected systems using Kotlin
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"))
}
}
}
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("/") }
}
...
}
# In application.conf
ktor {
deployment {
port = 8080
watch = [ main, module1, module2 ]
}
…
}
Then just build the app!
fun Application.module(testing: Boolean = false) {
install(Compression) {
gzip {
priority = 1.0
}
deflate {
priority = 10.0
minimumSize(1024) // condition
}
}
...
}
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>()
}
}
}
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"))
}
...
}
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" }
}
}
}
}
}
...
}
}
Support for simplifying and structuring
request handling
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)
}
}
}
}
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()
}
}
A pragmatic lightweight dependency injection framework for Kotlin
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())
}
...
}
}
A lightweight SQL library for Kotlin, by Jetbrains
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
}
}
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