Kora облачно ориентированный серверный фреймворк написанный на Java для написания Java / Kotlin приложений с упором на производительность, эффективность, прозрачность сделанный выходцами из Т-Банк / Тинькофф

Kora is a cloud-oriented server-side Java framework for writing Java / Kotlin applications with a focus on performance, efficiency and transparency

Перейти к содержанию
V1 V2

Проект и сборка

Kora - облачно ориентированный серверный фреймворк, написанный на Java, для приложений на Java и Kotlin. Эта страница описывает базовые принципы Kora, требования к окружению, подключение обработчиков аннотаций, минимальную настройку Gradle, управление зависимостями и запуск приложения.

Kora предоставляет набор модулей для быстрого создания серверных приложений: HTTP-сервер и HTTP-клиент, потребители Kafka, репозитории для работы с базами данных, S3-клиент, gRPC-сервер и gRPC-клиент, интеграции с Camunda, телеметрию модулей, отказоустойчивость и другие возможности. Основные характеристики фреймворка описаны на главной странице.

Kora предоставляет инструменты, которые обычно нужны современной серверной разработке:

  • внедрение зависимостей через аннотации;
  • инверсию управления без отдельного контейнера во время выполнения;
  • аспектно-ориентированное программирование через аннотации;
  • достаточно высокоуровневые простые абстракции и инструменты разработки;
  • большой набор заранее настроенных интеграций;
  • телеметрию, трассировку, метрики по стандарту OpenTelemetry и логирование модулей;
  • быстрое тестирование с помощью JUnit5;
  • рабочие примеры и руководства.

Для высокопроизводительного, эффективного и предсказуемого кода Kora следует нескольким принципам:

  • не использует Reflection во время работы приложения;
  • не использует динамический прокси во время работы приложения;
  • не генерирует байт-код во время компиляции или работы приложения;
  • создает исходный код на этапе компиляции через обработчики аннотаций;
  • оставляет тонкие абстракции над интеграциями;
  • предоставляет бесплатные аспекты: без дополнительной стоимости во время работы приложения;
  • использует только наиболее эффективные реализации для интеграций;
  • поощряет и использует наиболее эффективные принципы разработки и естественные конструкции языка.

Kora исполняет код приложения синхронно на виртуальных потоках. Контроллеры, HTTP-клиенты, репозитории и запланированные задачи объявляются обычными блокирующими сигнатурами, а диспетчеризацией на виртуальные потоки занимается сам фреймворк: например, HTTP-запрос обрабатывается на виртуальном потоке, привязанном к его соединению. Реактивных и suspend-контрактов в модулях Kora нет - обработчики отклоняют suspend-методы контроллеров, клиентов, репозиториев и планировщика ошибкой компиляции. Если в рамках одной операции нужно выполнить несколько действий параллельно, используйте StructuredTaskScope из Java structured concurrency; это preview-API, поэтому и компиляция, и каждый запуск JVM требуют --enable-preview.

Если нужен пошаговый разбор перед справочным описанием, смотрите Создание первого приложения на Kora и Введение во внедрение зависимостей.

Обработчики аннотаций

Kora строит приложение на этапе компиляции: обработчики читают аннотации, проверяют код и генерируют исходные файлы, которые затем компилируются вместе с кодом приложения. За счет этого граф зависимостей, аспекты, HTTP-обработчики, репозитории и другие компоненты становятся обычным скомпилированным кодом без Reflection во время работы.

Аннотация - это конструкция, связанная с элементами исходного кода Java: классами, методами, параметрами и полями. Обработчик аннотаций запускается компилятором, читает эти аннотации и может сгенерировать дополнительный исходный код или остановить компиляцию с понятной ошибкой.

Kora предоставляет все обработчики аннотаций в одной зависимости:

annotationProcessor "io.koraframework:annotation-processors"

Эта зависимость нужна только на этапе компиляции и не добавляет лишние библиотеки в путь классов времени выполнения приложения.

Для Kotlin используется KSP (Kotlin Symbol Processing). KSP читает символы исходного кода Kotlin, передает их процессорам Kora и позволяет генерировать код до основной компиляции.

Kora предоставляет KSP-обработчики в одной зависимости:

ksp("io.koraframework:symbol-processors")

При этом обработка Kotlin обычно медленнее обработки аннотаций в Java.

KSP

KSP нужен только для Kotlin-проектов. Если приложение написано на Java, используйте обычный annotationProcessor; если приложение написано на Kotlin, подключайте com.google.devtools.ksp и зависимость io.koraframework:symbol-processors.

KSP складывает сгенерированные исходники в build/generated/ksp/main/kotlin и build/generated/ksp/test/kotlin. Плагин KSP для Gradle сам добавляет эти каталоги в компиляцию; в файлах сборки на этой странице они дополнительно объявлены в исходных наборах явно:

kotlin {
    sourceSets.main { kotlin.srcDir("build/generated/ksp/main/kotlin") }
    sourceSets.test { kotlin.srcDir("build/generated/ksp/test/kotlin") }
}

Если перед генерацией кода должна отработать другая задача (например, генерация OpenAPI или protobuf), привязывайте ее к задачам KSP по имени. В KSP 2 тип KspTask больше не доступен, поэтому конструкция tasks.withType<KspTask>() не работает:

tasks.matching { it.name.startsWith("ksp") }.configureEach {
    dependsOn(openApiGenerateHttpServer)
}

Совместимость

Артефакты Kora компилируются и публикуются под Java 25: и Java-, и Kotlin-часть фреймворка собираются с sourceCompatibility/targetCompatibility 25 и jvmTarget 25, а публикуемый BOM объявляет java.version 25. Поэтому JDK 25 - минимальная версия для компиляции и запуска приложения на Kora независимо от языка.

Требуется версия не ниже JDK 25, рекомендуется использовать последний доступный GA-релиз JDK.

Минимальная конфигурация в build.gradle:

plugins {
    id "java"
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(25)
        vendor = JvmVendorSpec.ADOPTIUM
    }
}

Указание vendor необязательно и просто соответствует toolchain Adoptium, который используется в примерах проектов; его можно опустить или выбрать другого поставщика.

Требуется версия не ниже JDK 25, рекомендуется использовать последний доступный GA-релиз JDK.

Используйте те же версии, на которых собран сам фреймворк: Kotlin 2.4.10 и KSP 2.3.11. Расхождение между компилятором Kotlin и компилятором, встроенным в KSP, приводит к труднодиагностируемым падениям обработчиков символов, поэтому обе версии закрепляются вместе.

Минимальная конфигурация в build.gradle.kts:

plugins {
    kotlin("jvm") version "2.4.10"
    id("com.google.devtools.ksp") version "2.3.11"
}

kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(25))
        vendor.set(JvmVendorSpec.ADOPTIUM)
    }
    sourceSets.main { kotlin.srcDir("build/generated/ksp/main/kotlin") }
    sourceSets.test { kotlin.srcDir("build/generated/ksp/test/kotlin") }
}

Самому процессу Gradle тоже нужен достаточно новый JDK

toolchain в Gradle влияет только на компиляцию и запуск приложения, а classpath buildscript разрешается той JVM, на которой работает сам Gradle. Как только туда попадает артефакт Kora - чаще всего это io.koraframework:openapi-generator для генерации кода по OpenAPI - сборка на старой JVM падает уже на стадии конфигурации:

Dependency requires at least JVM runtime version 25. This build uses a Java 21 JVM.

Запускайте Gradle на JDK 25+ через JAVA_HOME или через настройки JVM демона Gradle. Не прописывайте org.gradle.java.home в gradle.properties репозитория: этот путь зависит от конкретной машины.

Автоматическая загрузка JDK для toolchain

Чтобы Gradle мог сам скачать недостающий JDK для toolchain, в примерах проектов Kora подключен резолвер в settings.gradle:

plugins {
    id "org.gradle.toolchains.foojay-resolver-convention" version "1.0.0"
}

и включена загрузка в gradle.properties:

org.gradle.java.installations.auto-detect=true
org.gradle.java.installations.auto-download=true

Нуллабельность

В Java Kora размечает нуллабельность через JSpecify - org.jspecify.annotations.Nullable, которая приходит транзитивно с любым модулем Kora. Это type-use-аннотации, поэтому их позиция значима: Outer.@Nullable Inner, List<@Nullable String>, String @Nullable []. В Kotlin нуллабельность выражается самим типом (T?) и никакой аннотации не требуется; при переопределении контракта Kora, параметр которого помечен @Nullable, объявляйте параметр нуллабельным.

Система сборки

Kora рассчитана на сборку через Gradle, потому что Gradle хорошо поддерживает обработчики аннотаций, KSP, инкрементальную сборку и управление зависимостями. Сам фреймворк и все примеры проектов Kora собираются на Gradle 9.5.1, поэтому рекомендуемая версия - Gradle 9.5+.

Чтобы не указывать версии для каждой зависимости Kora отдельно, используется BOM io.koraframework:kora-bom. Версия BOM задается один раз, а остальные зависимости Kora подключаются без явного указания версии.

Минимальная конфигурация приложения в build.gradle:

plugins {
    id "java"
    id "application"
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(25)
        vendor = JvmVendorSpec.ADOPTIUM
    }
}

configurations {
    koraBom //(1)!
    annotationProcessor.extendsFrom(koraBom)
    implementation.extendsFrom(koraBom)
    testAnnotationProcessor.extendsFrom(koraBom)
}

dependencies {
    koraBom platform("io.koraframework:kora-bom:2.0.0.RC1")

    annotationProcessor "io.koraframework:annotation-processors"

    implementation "io.koraframework:config-hocon"
    implementation "io.koraframework:http-server-undertow"
    implementation "io.koraframework:json-common"
    implementation "io.koraframework:logging-logback"
}

  1. Отдельная конфигурация koraBom, которую наследуют все остальные. platform(), подключенный только к implementation, не дошел бы до annotationProcessor и testAnnotationProcessor, и тогда зависимость обработчика пришлось бы указывать с явной версией.

Более подробный пример есть в руководстве по созданию первого приложения.

Для Kotlin предполагается Gradle Kotlin DSL. Если проект использует Groovy DSL, ориентируйтесь на примеры для Java.

Минимальная конфигурация приложения в build.gradle.kts:

plugins {
    id("application")
    kotlin("jvm") version "2.4.10"
    id("com.google.devtools.ksp") version "2.3.11"
}

kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(25))
        vendor.set(JvmVendorSpec.ADOPTIUM)
    }
    sourceSets.main { kotlin.srcDir("build/generated/ksp/main/kotlin") }
    sourceSets.test { kotlin.srcDir("build/generated/ksp/test/kotlin") }
}

dependencies {
    implementation(platform("io.koraframework:kora-bom:2.0.0.RC1")) //(1)!

    ksp("io.koraframework:symbol-processors:2.0.0.RC1") //(2)!

    implementation("io.koraframework:config-hocon")
    implementation("io.koraframework:http-server-undertow")
    implementation("io.koraframework:json-common")
    implementation("io.koraframework:logging-logback")
}

  1. В Kotlin-проектах BOM подключается прямо к implementation, отдельная конфигурация koraBom с extendsFrom не создается.
  2. Конфигурация ksp не покрывается BOM, поэтому версия обработчика указывается явно.

Более подробный пример есть в руководстве по созданию первого приложения.

В реальных проектах версию BOM обычно выносят в свойство gradle.properties (например koraVersion) и ссылаются на нее как platform("io.koraframework:kora-bom:$koraVersion"), чтобы версия объявлялась в одном месте, а не была прописана в каждом модуле.

Доступ к внутренностям компилятора

Некоторые обработчики аннотаций Java читают внутренние компоненты jdk.compiler. На новых версиях JDK для этого может потребоваться экспортировать соответствующие пакеты компилятору. Все примеры проектов Kora задают эти аргументы JVM в gradle.properties безусловно; добавьте их, если компиляция завершается ошибками IllegalAccessError или module jdk.compiler does not export ...:

org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
  --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
  --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
  --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
  --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \
  --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED

Зависимости

В документации модулей Kora обычно показывается только зависимость конкретного модуля. Но в приложении также должны быть подключены BOM и обработчики, показанные ниже.

build.gradle:

configurations {
    koraBom
    annotationProcessor.extendsFrom(koraBom)
    implementation.extendsFrom(koraBom)
    testAnnotationProcessor.extendsFrom(koraBom)
}

dependencies {
    koraBom platform("io.koraframework:kora-bom:2.0.0.RC1") //(1)!

    annotationProcessor "io.koraframework:annotation-processors" //(2)!
    testAnnotationProcessor "io.koraframework:annotation-processors" //(3)!
}
  1. BOM с версиями всех артефактов Kora (обязательно, без значения по умолчанию).
  2. Все обработчики аннотаций Kora для основных исходников (обязательно, без значения по умолчанию).
  3. Те же обработчики для тестовых исходников - нужны только если в тестах объявлены собственные аннотации Kora, например собственный @KoraApp (опционально).

build.gradle.kts:

dependencies {
    implementation(platform("io.koraframework:kora-bom:2.0.0.RC1")) //(1)!

    ksp("io.koraframework:symbol-processors:2.0.0.RC1") //(2)!
    kspTest("io.koraframework:symbol-processors:2.0.0.RC1") //(3)!
}
  1. BOM с версиями всех артефактов Kora (обязательно, без значения по умолчанию).
  2. Все KSP-обработчики Kora для основных исходников (обязательно, без значения по умолчанию).
  3. Те же обработчики для тестовых исходников - нужны только если в тестах объявлен собственный @KoraApp, например отдельный TestApplication (опционально).

После этого зависимости модулей можно указывать без версии, например:

implementation "io.koraframework:http-server-undertow"
implementation("io.koraframework:http-server-undertow")

Все артефакты Kora находятся в группе io.koraframework. Исключение - экспериментальные модули: декларативный S3-клиент s3-client-kora и интеграции с Camunda camunda-engine-bpmn, camunda-rest-undertow, camunda-zeebe-worker вместе с их обработчиками - они публикуются в группе io.koraframework.experimental.

Запуск

Приложение Kora - это интерфейс с аннотацией @KoraApp, который наследует нужные приложению модули. Обработчик генерирует рядом с ним класс, названный по имени интерфейса с суффиксом Graph, а его статический метод graph() возвращает описание графа зависимостей:

@KoraApp
public interface Application extends HoconConfigModule, JsonModule, LogbackModule, UndertowPublicHttpServerModule { //(1)!

    static void main(String[] args) {
        KoraApplication.run(ApplicationGraph::graph); //(2)!
    }
}
  1. Аннотация @KoraApp лежит в io.koraframework.common.annotation, а модули приходят из подключенных артефактов Kora.
  2. ApplicationGraph генерируется обработчиком по имени интерфейса Application и попадает в тот же пакет. KoraApplication.run принимает это описание (ApplicationGraphDraw), инициализирует граф, регистрирует обработчик остановки и блокирует поток до завершения работы приложения.
@KoraApp
interface Application : HoconConfigModule, JsonModule, LogbackModule, UndertowPublicHttpServerModule //(1)!

fun main() {
    KoraApplication.run(ApplicationGraph::graph) //(2)!
}
  1. Аннотация @KoraApp лежит в io.koraframework.common.annotation, а модули приходят из подключенных артефактов Kora.
  2. ApplicationGraph генерируется обработчиком по имени интерфейса Application и попадает в тот же пакет. KoraApplication.run принимает это описание (ApplicationGraphDraw), инициализирует граф, регистрирует обработчик остановки и блокирует поток до завершения работы приложения.

Для локального запуска и сборки исполняемого архива обычно используется плагин application.

[!TIP] Рекомендуется всегда использовать фиксированные значения applicationName = "application" и archiveFileName = "application.tar" — это упрощает работу с архивом в Dockerfile и CI/CD-скриптах, так как имя файла не зависит от версии проекта.

Подключите плагин в build.gradle:

plugins {
    id "application" //(1)!
}

  1. Плагин application предоставляет задачи для запуска и сборки исполняемого архива (по умолчанию не подключен, опционально). Подробнее в документации Gradle.

Системные свойства и переменные окружения для локального запуска можно задать в задаче run:

run {
    jvmArgs += [
        "-Xmx256m", //(1)!
    ]

    environment([
        "SOME_ENV": "someValue", //(2)!
    ])
}

  1. JVM-аргументы для запуска приложения (по умолчанию не указаны, опционально)
  2. Переменные окружения, доступные в приложении (по умолчанию не указаны, опционально)

Запуск:

./gradlew run

Настройка сборки архива:

application {
    applicationName = "application" //(1)!
    mainClass = "io.koraframework.example.Application" //(2)!
    applicationDefaultJvmArgs = ["-Dfile.encoding=UTF-8"] //(3)!
}

distTar {
    archiveFileName = "application.tar" //(4)!
}

  1. Имя приложения, используется для именования скриптов (по умолчанию: имя проекта). Рекомендуется фиксировать значение "application" для упрощения работы в Dockerfile и CI/CD.
  2. Полное имя класса с методом main для запуска (по умолчанию не указан, обязательно).
  3. JVM-аргументы по умолчанию для запуска (по умолчанию не указаны, опционально). Если приложение использует StructuredTaskScope, сюда также нужно добавить --enable-preview.
  4. Имя файла архива (по умолчанию: <applicationName>-<version>.tar). Рекомендуется фиксировать значение "application.tar" для упрощения работы в Dockerfile и CI/CD. Подробнее в документации по задаче Tar.

Сборка архива:

./gradlew distTar

Пример настроенного приложения можно посмотреть в шаблоне Java-приложения.

Подключите плагин в build.gradle.kts:

plugins {
    id("application") //(1)!
    kotlin("jvm") version "2.4.10"
    id("com.google.devtools.ksp") version "2.3.11"
}

  1. Плагин application предоставляет задачи для запуска и сборки исполняемого архива (по умолчанию не подключен, опционально)

Системные свойства и переменные окружения для локального запуска можно задать в задачах JavaExec:

tasks.withType<JavaExec> {
    jvmArgs(
        "-Xmx256m", //(1)!
    )

    environment(
        "SOME_ENV" to "someValue", //(2)!
    )
}

  1. JVM-аргументы для запуска приложения (по умолчанию не указаны, опционально)
  2. Переменные окружения, доступные в приложении (по умолчанию не указаны, опционально)

Запуск:

./gradlew run

Настройка сборки архива:

application {
    applicationName = "application" //(1)!
    mainClass.set("io.koraframework.example.ApplicationKt") //(2)!
    applicationDefaultJvmArgs = listOf("-Dfile.encoding=UTF-8") //(3)!
}

tasks.distTar {
    archiveFileName.set("application.tar") //(4)!
}

  1. Имя приложения, используется для именования скриптов (по умолчанию: имя проекта). Рекомендуется фиксировать значение "application" для упрощения работы в Dockerfile и CI/CD.
  2. Полное имя класса с методом main для запуска (по умолчанию не указан, обязательно); для Kotlin это класс с суффиксом Kt.
  3. JVM-аргументы по умолчанию для запуска (по умолчанию не указаны, опционально). Если приложение использует StructuredTaskScope, сюда также нужно добавить --enable-preview.
  4. Имя файла архива (по умолчанию: <applicationName>-<version>.tar). Рекомендуется фиксировать значение "application.tar" для упрощения работы в Dockerfile и CI/CD. Подробнее в документации по задаче Tar.

Сборка архива:

./gradlew distTar

Пример настроенного приложения можно посмотреть в шаблоне Kotlin-приложения.

Терминология

В этой секции описаны базовые термины, которые встречаются в документации Kora:

  • Фабрика - метод, который создает и возвращает экземпляр компонента или зависимости.
  • Модуль - подключаемая зависимость или интерфейс с фабричными методами, которые добавляют в приложение новые компоненты.
  • Компонент - объект в графе зависимостей Kora. Обычно это единственный экземпляр класса, который реализует часть логики приложения.
  • Аспект - логика, которая расширяет поведение метода до, после или вокруг его выполнения на основании аннотации.
  • Граф зависимостей - набор компонентов приложения и связей между ними, построенный Kora на этапе компиляции.

Первое руководство

После общего обзора переходите к руководству Создание первого приложения на Kora. В нем базовая структура приложения показана на небольшом HTTP-сервисе, который можно собрать и запустить.