Kotlin-сервер без JVM — реальность?

Kate

Administrator
Команда форума
Не секрет, что Kotlin может использоваться для создания всех компонентов FullStack-приложения - от мобильных приложения для Android/iOS и веб-сайтов на Kotlin JS до бэкэнда (например, с использованием Ktor, http4k и micronaut). Но все же многих останавливает от использования Kotlin для создания API тот факт, что код запускается в хоть и оптимизированной, но все же Java виртуальной машине. Есть ли решение у этой проблемы? Да, и в этой статье мы обсудим способы компиляции приложения на Kotlin для создания API в нативный код и подводные камни, которые нас ожидают на этом пути.

Прежде всего начнем с того, как Kotlin-код может быть преобразован в исполняемый файл для Windows/Linux или MacOS. В действительности компиляции в двоичный образ выполняется в два этапа и здесь используются возможности LLVM и специальные реализации frontend (компиляция Kotlin в промежуточное представление IR) и backend (компиляция IR в двоичный файл). Благодаря двухэтапной компиляции возможно разделить этап синтаксического анализа и компиляции кода в некоторое универсальное представление и дальнейшее преобразование под конкретную целевую платформу. Более подробно некоторые аспекты разработки на Kotlin Native для desktop-приложений мы рассматривали в этой статье.

С точки зрения конфигурирования единственное отличие Kotlin Native-модуля в multiplatform-приложении от JVM-приложения будет другая целевая платформа в блоке kotlin в build.gradle. Например, для сборки под Linux конфигурация будет описана в linuxX64. Также можно использовать информацию о текущей платформе из System.getProperty("os.name"):

plugins {
kotlin("multiplatform") version "1.8.0"
}

group = "org.example"
version = "1.0-SNAPSHOT"

repositories {
mavenCentral()
}

kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
nativeTarget.apply {
binaries {
executable()
}
}
//определим также расположение исходных текстов модулей
sourceSets {
val nativeMain by getting {
//здесь будут зависимости и конфигурация сборки
}
val nativeMain by getting
}
}
Веб-сервер Ktor может использоваться в native target с некоторыми ограничениями, прежде всего возможно использовать только движок CIO и отсутствует поддержка SSL непосредственно в процессе сервера (но он может быть выполнен через reverse proxy). Добавим необходимые зависимости (для веб-сервера и тестирования) и попробуем создать простую маршрутизацию:

sourceSets {
val nativeMain by getting {
dependencies {
implementation("io.ktor:ktor-server-core:2.2.3")
implementation("io.ktor:ktor-server-cio:2.2.3")
}
}
val nativeTest by getting {
dependencies {
implementation("io.ktor:ktor-server-test-host:2.2.3")
implementation("org.jetbrains.kotlin:kotlin-test:1.8.0")
}
}
}
Исходный текст сервера:

import io.ktor.server.application.*
import io.ktor.server.cio.*
import io.ktor.server.engine.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun main() {
embeddedServer(CIO, port = 8080) {
module()
}.start(wait = true)
}

fun Application.module() {
routing {
get("/") {
call.respondText("Hello")
}
}
}
И сразу создадим тест для него:

import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlin.test.Test
import kotlin.test.assertEquals

class ApplicationTest {
@Test
fun testRoot() = testApplication {
application {
module()
}
val response = client.get("/")
assertEquals(HttpStatusCode.OK, response.status)
assertEquals("Hello", response.bodyAsText())
}
}
Для запуска сервера будем использовать gradle-задачу runDebugExecutableNative (также создаст исполняемый файл в формате целевой платформы в build/bin/native/debugExecutable *.kexe). При запуске тестов (gradle nativeTest) также будет создаваться исполняемый файл, в который интегрируется Native Test Runner и результат компиляции исходного приложения и тестового сценария (build/bin/native/debugTest/test.kexe).

Для корректной работы ktor требуется новая модель памяти (включена по умолчанию в Kotlin 1.7.20 и более новых). Также нам будет доступна возможность использования всех функций и расширений из стандартной библиотеки Kotlin (включая корутины, поддержку коллекций и др.).

В реальной ситуации для веб-сервера вряд ли будет достаточно возвращать константные строки и в минимальном варианте будет необходимо использовать библиотеки для сериализации JSON (для нативной разработки можно использовать kotlinx.serialization). Также можно использовать библиотеки, которые имеют вариант сборки для нативной платформы или любые библиотеки, не использующие какие-либо специфичные для Java классы и методы. Список библиотек например можно посмотреть здесь или здесь.

Из расширений ktor для native-сервера можно использовать следующие:

  • HTML/CSS DSL
  • Basic Authentication
  • Bearer Authentication
  • Form Based Authentication
  • Sessions
  • Caching Headers, Conditional Headers
  • CORS
  • HSTS
  • Partial Content
  • Data conversion (с kotlinx.serialization)
  • WebSockets
  • и ряд других
Для библиотек, которые не представлены в native target, можно попробовать интегрировать исходными текстами, либо реализовать самостоятельно (для этого можно использовать Ktor Client для сетевых запросов и все возможности стандартной библиотеки Kotlin, включая сериализацию). Также существуют библиотеки для организации Key-Value хранилищ (например, multiplatform-settings, Kodein-DB, KStore или KVault) и DI (Kodein, Koin, Inject) и количество библиотек постепенно увеличивается.

Основной проблемой реализации реальных веб-серверов в Kotlin Native на данный момент являются сложности с поддержкой драйверов для баз данных, однако это быть решено через создание (или использование готовых исходных кодов) библиотек на Kotlin с заменой сетевого слоя с библиотек Java на использование Ktor Client или аналогичных мультиплатформенных клиентов. В остальном создание нативных реализаций серверов доступно уже сейчас и для высокопроизводительных задач можно рассматривать сочетание Kotlin Native + Ktor + KStore для хранения данных.

 
Сверху