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

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

31 июля 2026

Фразу фреймворк с этапом компиляции легко понять неправильно.

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

Этот вывод не описывает, как Kora на самом деле работает.

Фреймворк Kora избегает отражения, обнаружения зависимостей рантайма, динамических прокси и генерации байткода рантайма в тех частях стека, которыми управляет сама Kora. Он строит граф приложения на этапе компиляции, генерирует репозитории и HTTP-адаптеры, производит AOP-подклассы этапа компиляции, генерирует код маппинга и превращает связку фреймворка в обычный байткод Java или Kotlin до запуска процесса.

Это стратегия реализации Kora.

Это не запрет на уровне всего приложения для обычного поведения JVM.

Приложение на Kora — всё ещё обычное Java- или Kotlin-приложение, работающее на обычной JVM. Библиотека на classpath всё ещё может вызывать Class.forName, инспектировать аннотации через отражение, вызывать методы через Method.invoke, создавать экземпляры java.lang.reflect.Proxy, динамически загружать ресурсы, использовать ServiceLoader, создавать классы рантайма через библиотеку байткода или зависеть от другой библиотеки, которая делает что-либо из этого. На HotSpot эти механизмы остаются обычными механизмами JVM.

Это ключевое различие:

модель рантайма Kora
        ≠
модель рантайма сторонней библиотеки
        ≠
ограничения GraalVM Native Image

Смешивание этих трёх слоёв производит удивительно большое количество неверных выводов о совместимости Kora.

Правильная модель гораздо проще:

Kora избегает отражения и динамических прокси в собственной модели рантайма, но это не значит, что приложениям, построенным на Kora, запрещено использовать обычные Java-библиотеки, которые опираются на отражение, прокси или метаданные рантайма. Kora — всё ещё обычный JVM-фреймворк, работающий на обычном JVM-байткоде.

Как только это различие ясно, вместе с ним исчезают и несколько других мифов.

Стратегия реализации фреймворка — это не ограничение всего приложения

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

Для внедрения зависимостей это означает построение и валидацию графа во время компиляции.

Для декларативных HTTP-клиентов это означает генерацию реализации вместо создания рефлексивного прокси-клиента при запуске.

Для репозиториев это означает генерацию кода реализации для исполнения SQL и маппинга.

Для AOP это означает генерацию подклассов вокруг компонентов приложения вместо конструирования перехватывающих прокси рантайма.

Для сериализации и маппинга это означает генерацию читателей, писателей и мапперов там, где фреймворк владеет этой абстракцией.

Это можно резюмировать так:

Механика, принадлежащая фреймворку
        ↓
разрешается на этапе компиляции, где полезно
        ↓
генерируется обычный исходный код
        ↓
выполняется обычный байткод

Этот выбор улучшает предсказуемость, потому что фреймворку не нужно заново обнаруживать собственную модель приложения после запуска JVM. Он также делает сгенерированное поведение инспектируемым, избегает большей части механизмов фреймворка рантайма и в целом хорошо подходит для GraalVM Native Image.

Но ничто из этого не меняет семантику самой платформы Java.

Kora не устанавливает менеджер безопасности, отключающий отражение.

Она не переписывает JDK.

Она не инструментирует сторонние зависимости и не заставляет их принимать дизайн Kora.

Она не требует, чтобы каждая библиотека в графе зависимостей раскрывала метаданные этапа компиляции или сгенерированные фабрики.

Фреймворк принимает решение о том, как Kora реализует Kora.

Он не законодательствует, как каждая другая библиотека реализует себя.

Это различие фундаментально.

«Kora избегает отражения» — это не то же самое, что «отражение отключено»

Это, вероятно, самое важное заблуждение.

Утверждение:

Kora избегает отражения во время выполнения

означает:

Kora не нуждается в отражении
для собственных обычных механизмов фреймворка рантайма.

Оно не означает:

JVM-процесс не способен к отражению.

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

Class<?> type = Class.forName(className);
var constructor = type.getDeclaredConstructor();
constructor.setAccessible(true);

Object value = constructor.newInstance();
val type = Class.forName(className)
val constructor = type.getDeclaredConstructor()
constructor.isAccessible = true

val value = constructor.newInstance()

На обычной JVM приложение на Kora исполняет этот код ровно как любое другое Java-приложение, с учётом обычных правил доступа к модулям Java и поведения JDK.

Предположим, другая библиотека делает так:

Object proxy = Proxy.newProxyInstance(
    interfaceType.getClassLoader(),
    new Class<?>[]{interfaceType},
    invocationHandler
);
val proxy = Proxy.newProxyInstance(
    interfaceType.classLoader,
    arrayOf<Class<*>>(interfaceType),
    invocationHandler
)

Опять же, на HotSpot нет ничего в Kora, что отключало бы Proxy.newProxyInstance.

Третья библиотека может сканировать аннотации:

for (var annotation : target.getAnnotations()) {
    // инспектировать метаданные
}
for (annotation in target.annotations) {
    // инспектировать метаданные
}

Это тоже продолжает работать.

Собственный код фреймворка Kora не становится вдруг рефлексивным только потому, что такая библиотека присутствует, а внешняя библиотека не становится вдруг нерефлексивной только потому, что присутствует Kora.

Две стратегии реализации сосуществуют в одном процессе.


Реальная картина рантайма

Реалистичный сервис на Kora легко может выглядеть так:

Приложение Kora
│
├── сгенерированный граф зависимостей Kora
│
├── сгенерированные HTTP-маппинги
│
├── сгенерированные репозитории
│
├── сгенерированные AOP-подклассы
│
├── сгенерированная сериализация/маппинг
│
├── бизнес-код
│
├── обычная Java-библиотека A
│      └── использует отражение внутри
│
├── обычная Java-библиотека B
│      └── использует динамические прокси внутри
│
├── обычная Java-библиотека C
│      └── динамически загружает ресурсы
│
└── обычная Java-библиотека D
       └── использует ServiceLoader

Здесь нет противоречия.

Часть рантайма Kora остаётся сгенерированной и явной. Сторонние библиотеки продолжают использовать собственные стратегии реализации. Именно так обычно работает JVM-экосистема: разные библиотеки делают разные компромиссы. Одна библиотека может предпочитать отражение, потому что её модель по своей природе динамична. Другая может использовать генерацию байткода, потому что хочет специализацию рантайма. Другая может использовать сгенерированные классы, произведённые её собственным процессором аннотаций. Другая может использовать написанный вручную прямой Java.

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


Собственный HTTP-стек Kora — хороший контрпример мифу о чистоте

Сама Kora демонстрирует, что её архитектура избирательна, а не идеологична.

Её абстракция HTTP-клиента может использовать несколько реализаций транспорта. Текущая документация Kora 2 включает транспорты на основе OkHttp, Apache HttpClient 5 и нативную реализацию. OkHttp — зрелая внешняя библиотека с собственной внутренней архитектурой, зависимостями, пулом соединений, обработкой протокола, поведением TLS, поддержкой HTTP/2 и HTTP/3. Apache HttpClient — ещё один зрелый внешний стек с собственными абстракциями и внутренностями рантайма.

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

Вместо этого архитектура примерно такова:

декларативный клиент Kora
        ↓
сгенерированный адаптер запроса/ответа Kora
        ↓
абстракция HttpClient Kora
        ↓
OkHttp / Apache HttpClient / нативный транспорт

Верхняя часть может генерироваться на этапе компиляции. Транспорт остаётся транспортом. Это важная подсказка о реальной философии Kora. Если бы чистота этапа компиляции была целью, использование внешнего транспорта, чья стратегия реализации вне контроля Kora, было бы архитектурно подозрительным. Вместо этого Kora явно его поддерживает. Практическое правило ближе к этому:

**Там, где Kora владеет абстракцией, она может удалять ненужные механизмы рантайма. Там, где существующая Java-библиотека уже хорошо решает проблему, интегрируйте её, а не воспроизводите экосистему. **

Это очень отличается от философии «всё должно быть на этапе компиляции».


Фреймворк с этапом компиляции не означает закрытую JVM

Полезно мыслить в терминах границ.

Kora контролирует:

граф приложения
сгенерированную связку зависимостей
сгенерированные аспекты фреймворка
сгенерированные репозитории
сгенерированные HTTP-контракты
сгенерированный код маппинга
интеграцию жизненного цикла фреймворка
интеграцию телеметрии фреймворка

Kora не контролирует каждую деталь реализации:

драйвера базы данных
пула JDBC
клиента Kafka
рантайма gRPC
OkHttp
Apache HttpClient
Logback
Netty
облачного SDK вендора
библиотеки безопасности
библиотеки сериализации
нативного SDK
проприетарной библиотеки компании

Эти библиотеки остаются обычными Java-зависимостями.

Это не случайный запасной люк. Это необходимо для любого практичного JVM-фреймворка.

Java-экосистема слишком велика и слишком неоднородна, чтобы фреймворк владел моделью реализации каждой зависимости.

Поэтому полезная архитектурная цель — не единообразие.

Это локальная оптимизация.


Три вопроса, которые никогда не следует смешивать

Оценивая библиотеку для сервиса на Kora, задайте три отдельных вопроса.

1. Работает ли библиотека на JVM?

Это обычный вопрос совместимости.

Поддерживает ли она версию JDK?

Подходит ли она под модель потоков?

Соответствует ли её API потребностям приложения?

Приемлемы ли её лицензирование, производительность и обслуживание?

Конфликтует ли она с другими зависимостями?

Это тот же вид вопроса, который задаёт любое Java-приложение.

2. Чисто ли библиотека интегрируется в граф приложения Kora?

Это гораздо меньший вопрос.

Можно ли сконструировать её основной клиент или сервис через фабрику?

Нужна ли ей конфигурация?

Нужен ли ей жизненный цикл?

Нужны ли ей телеметрия или пробы?

Раскрывает ли она обычные Java-объекты, которые можно внедрять?

Во многих случаях ответ тривиален:

Config
  ↓
фабрика
  ↓
нативный клиент
  ↓
@Module
  ↓
граф приложения

3. Работает ли библиотека в GraalVM Native Image?

Это отдельный вопрос развёртывания.

Использует ли она отражение?

Загружает ли классы динамически?

Генерирует ли байткод во время выполнения?

Использует ли JNI?

Зависит ли от ресурсов, которые Native Image не может обнаружить автоматически?

Поставляет ли метаданные достижимости?

Покрывает ли её репозиторий метаданных GraalVM?

Эти три вопроса иногда пересекаются, но они не эквивалентны.

Библиотека может:

отлично работать на HotSpot
+
тривиально интегрироваться с Kora
+
требовать метаданные Native Image

В этой комбинации нет ничего необычного.


HotSpot и Native Image — разные модели развёртывания

Различие становится яснее всего, когда задействовано отражение.

На обычной JVM:

JVM HotSpot
      ↓
библиотека с тяжёлым отражением
      ↓
обычное поведение JVM

Информация отражения остаётся доступной, потому что классы и метаданные существуют во время выполнения, и VM может выполнять рефлексивный поиск динамически.

С Native Image:

GraalVM Native Image
      ↓
анализ закрытого мира
      ↓
библиотека с тяжёлым отражением
      ↓
может требовать метаданные достижимости

Native Image выполняет анализ заранее и не всегда может вывести рефлексивный доступ, который собирается динамически из строк, файлов конфигурации, реестров плагинов или потока управления рантайма.

Это значит, что библиотеке могут понадобиться дополнительные метаданные, описывающие:

  • классы, доступные через отражение;
  • конструкторы;
  • методы;
  • поля;
  • динамические прокси;
  • ресурсы;
  • сериализацию;
  • JNI;
  • инициализацию классов рантайма.

Это задача GraalVM.

Она существовала бы, даже если бы приложение использовало другой фреймворк.

Kora лишь начинает с архитектуры фреймворка, которая сама по себе уже создаёт меньше этого бремени.


Совместимость с Kora и совместимость с Native Image — разные вопросы

Это стоит сказать прямо:

Совместимость с Kora и совместимость с Native Image не следует рассматривать как один и тот же вопрос.

Предположим, библиотека использует:

Class.forName(...)
Class.forName(...)

на основе свойства конфигурации.

На HotSpot это может быть совершенно обычным. Граф Kora может создавать библиотеку через фабрику:

@Module
public interface VendorModule {

    default VendorClient vendorClient(VendorConfig config) {
        return VendorClient.create(config);
    }
}
@Module
interface VendorModule {

    fun vendorClient(config: VendorConfig): VendorClient {
        return VendorClient.create(config)
    }
}

Приложение работает нормально.

Если тот же сервис позже компилируется в нативный исполняемый файл, GraalVM может понадобиться знать, какие классы могут упоминаться через этот путь Class.forName. Это не проблема моста Kora. Это проблема достижимости. Интеграция в Kora может по-прежнему состоять из одного фабричного метода.


Native Image — цель оптимизации, а не закон фреймворка

Сервису на Kora не обязательно работать как Native Image. Kora работает на обычной JVM.

Это важно, потому что обсуждения фреймворков с этапом компиляции часто незаметно скользят от:

Kora использует генерацию на этапе компиляции

к:

приложения на Kora должны быть нативными исполняемыми файлами

а затем к:

каждая зависимость должна удовлетворять ограничениям native-image

Это три отдельных решения.

Команда может выбрать:

Kora + HotSpot

потому что ценит:

  • быстрый запуск;
  • малые накладные расходы фреймворка;
  • прямое исполнение JVM;
  • виртуальные потоки;
  • сгенерированную связку;
  • обычную оптимизацию JIT.

Другая команда может выбрать:

Kora + Native Image

потому что задержка запуска, объём памяти, плотность развёртывания или среда serverless/scale-to-zero делают нативную компиляцию полезной.

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


Почему Kora хорошо подходит для Native Image

У Kora действительно есть реальное архитектурное преимущество, когда Native Image — требование.

Её собственный обычный рантайм не сильно зависит от механизмов, трудных для анализа закрытого мира:

  • сканирования classpath рантайма;
  • рефлексивного внедрения зависимостей;
  • генерации AOP-прокси рантайма;
  • динамически сгенерированных реализаций репозиториев;
  • генерации прокси HTTP-клиента рантайма.

Вместо этого Kora генерирует необходимый исходный код на этапе компиляции.

Это значит, что сам фреймворк вносит относительно мало скрытой достижимости.

Концептуально:

Типичный фреймворк с тяжёлым рантаймом

приложение
   ↓
обнаружение рантайма фреймворком
   ↓
метаданные отражения
   ↓
метаданные прокси
   ↓
поведение, сгенерированное рантаймом

против:

Kora

приложение
   ↓
сгенерированный исходный код
   ↓
обычный байткод

Это не гарантирует, что каждая зависимость приложения совместима с Native Image. Это значит, что фреймворк начинает с более чистой базы.

Это гораздо более точное преимущество.


Отражение сторонних библиотек существует даже внутри поддерживаемых модулей Kora

В собственной документации Native Image Kora есть особенно полезный пример.

Поддержка JDBC в Kora может использовать HikariCP. HikariCP может выполнять рефлексивный поиск в некоторых путях интеграции, включая конструирование, связанное с метриками. Kora обрабатывает это для Native Image, поставляя требуемые метаданные достижимости в артефакте модуля.

Это чрезвычайно показательно.

Если бы философия Kora была:

Отражения не может существовать нигде

существование таких метаданных было бы архитектурным нарушением.

Вместо этого подход Kora прагматичен:

Сама Kora избегает отражения рантайма
+
сторонняя зависимость использует отражение
+
метаданные native-image описывают эту зависимость

Здесь нет противоречия.

Фреймворк оптимизирует то, что контролирует, и интегрирует то, что не контролирует.


Logback — ещё один хороший пример

Библиотеки логирования часто выполняют обнаружение рантайма, разбор конфигурации, поиск плагинов или рефлексивное создание классов. Документация Native Image Kora явно обсуждает метаданные ресурсов и отражения для классов, связанных с логированием. Опять же, это демонстрирует, что Kora не притворяется, будто вся Java-экосистема следует одной модели рантайма.

Фактическая архитектура:

интеграция логирования Kora
       ↓
Logback
       ↓
собственное поведение рантайма Logback

При компиляции в Native Image метаданные добавляются там, где GraalVM нужна помощь.

На обычной JVM эти метаданные нерелевантны. JVM даже не потребляет каталог метаданных Native Image как часть обычного исполнения. Это разделение и есть суть.


Метаданные достижимости — это не «мост Kora»

Слово мост может сделать обычную интеграцию более сложной, чем она есть.

Для Native Image метаданные часто — просто описание динамического поведения, которое GraalVM не может вывести.

Примеры включают:

reflect-config.json
resource-config.json
proxy-config.json
serialization-config.json
jni-config.json

Это не заменяющий API.

Это не адаптер, переводящий библиотеку в концепции Kora. Это не значит, что приложение должно переписывать библиотеку. Это метаданные сборки для другой среды исполнения. Команде может понадобиться предоставлять или потреблять эти метаданные независимо от того, участвует ли Kora.


Репозиторий метаданных достижимости GraalVM меняет историю экосистемы

Совместимость с Native Image со временем также стала менее специфичной для фреймворка. Теперь существует механизм уровня экосистемы для обмена метаданными достижимости. Библиотека может поставлять метаданные сама. Интеграция фреймворка может поставлять метаданные для зависимости, о которой знает. Репозиторий метаданных достижимости GraalVM может предоставлять метаданные извне. Приложение может добавлять кастомные метаданные для собственных необычных путей. Это значит, что совместимость с Native Image всё больше выглядит как свойство зависимости, а не как требование к каждому фреймворку поддерживать проприетарную систему подсказок для всей Java-экосистемы. Kora выигрывает от этой экосистемы, а не пытается её заменить.


Трассировочный агент Native Image — ещё один запасной люк

Если динамическое поведение сторонней библиотеки ещё не покрыто, GraalVM предоставляет трассировочный агент, который может наблюдать рефлексивный доступ, загрузку ресурсов, генерацию прокси и связанное поведение, пока приложение работает на JVM.

Команда может прогнать релевантные сценарии и сгенерировать метаданные из наблюдаемого использования. Это не решает каждую проблему native-image автоматически, но демонстрирует правильную модель владения:

поведение библиотеки
      ↓
анализ / метаданные GraalVM

а не:

поведение библиотеки
      ↓
Kora должна перереализовать библиотеку

Модель развёртывания определяет работу по совместимости.


Динамическая загрузка классов — где различие становится серьёзнее

Отражение — не единственная задача Native Image.

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

Например:

строка конфигурации
      ↓
classloader
      ↓
загрузить произвольную реализацию

или:

схема рантайма
      ↓
генератор байткода
      ↓
новый класс

HotSpot обрабатывает эти паттерны естественно, потому что JVM остаётся открытой для загрузки классов рантайма. Native Image работает из допущения закрытого мира и поэтому не всегда может воспроизвести произвольную неизвестную загрузку рантайма. Если библиотека фундаментально зависит от загрузки байткода, которого не существовало при сборке нативного исполняемого файла, задача может быть архитектурной, а не просто связанной с метаданными.

Опять же, правильный вывод:

проблема:
модель исполнения Native Image

а не:

проблема:
DI этапа компиляции Kora

На стандартной JVM библиотека может быть вполне пригодной для использования с Kora.


JNI — тоже отдельное ограничение

JNI даёт ещё один пример того, почему три слоя нужно разделять. Библиотека может использовать нативный код через JNI и нормально работать на HotSpot. Kora может внедрять её клиент как любой другой Java-объект. Для Native Image могут требоваться дополнительная конфигурация и совместимость платформы. Ничто из этого не значит, что граф приложения Kora несовместим с JNI. Это значит, что у конечного исполняемого файла есть требование нативной интероперабельности.

То же различие применимо к:

  • JNA;
  • интеграциям на основе Panama;
  • нативным клиентам баз данных;
  • криптографическим провайдерам;
  • SDK оборудования;
  • библиотекам сжатия.

DI фреймворка и формат развёртывания — разные задачи.


Генерация байткода рантайма тоже не автоматически ломает Kora

Java-библиотеки иногда используют Byte Buddy, ASM, CGLIB, Javassist или подобные инструменты. На JVM такая библиотека может работать внутри приложения на Kora. Собственный рантайм Kora не начинает использовать прокси, сгенерированные рантаймом, только потому, что одна зависимость так делает. Издержки остаются локализованными.

Для Native Image генерация байткода рантайма может быть неподдерживаемой или требовать другого подхода.

Опять же:

совместимость с HotSpot
≠
совместимость с Native Image

Дизайн этапа компиляции Kora не схлопывает эти два понятия.


Одна рефлексивная зависимость не превращает Kora в рефлексивный фреймворк

Ещё одно распространённое преувеличение — что зависимость с тяжёлым отражением «уничтожает главное преимущество Kora». Это не то, как работает композиция производительности.

Стоимость приложения — примерно сумма многих компонентов:

Стоимость приложения
=
накладные расходы фреймворка
+ бизнес-код
+ работа с базой данных
+ сеть
+ сериализация
+ логирование
+ телеметрия
+ сторонние библиотеки
+ операционная система
+ внешние сервисы

Если одна библиотека выполняет дорогое отражение на конкретном пути исполнения, этот путь платит за него. Это не ретроактивно меняет то, как связан граф зависимостей. Это не превращает сгенерированные HTTP-маппинги в отражение рантайма. Это не превращает сгенерированные репозитории в прокси. Это не заставляет AOP Kora становиться динамическим.

Остальной стек остаётся тем, чем был.


Производительность локальна, прежде чем она глобальна

Предположим, сервис обрабатывает:

HTTP-запрос
   ↓
сгенерированный маршрут Kora
   ↓
сервис
   ↓
сторонний движок правил
   ↓
репозиторий

Если движок правил активно использует отражение, общая задержка запроса может включать эту стоимость.

Но декомпозиция остаётся:

стоимость маршрута Kora
+
стоимость движка правил
+
стоимость репозитория

Если движок правил становится бутылочным горлышком, профилируйте движок правил. Замена DI этапа компиляции Kora на отражение рантайма не сделала бы движок правил дешевле. Аналогично, присутствие движка правил не стирает экономию в других местах.

Это нормальный способ анализа производительности систем.


Производительность запуска тоже композиционна

То же самое применимо при запуске.

Kora может запускаться быстро, потому что её граф и адаптеры фреймворка уже сгенерированы. Сторонняя библиотека может затем потратить две секунды на сканирование classpath или загрузку метаданных. Время запуска приложения становится:

инициализация Kora
+
инициализация сторонней библиотеки

Библиотека может доминировать в общем времени.

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


Стоимость памяти тоже аддитивна

Предположим, Kora использует мало метаданных рантайма для DI и AOP, но сторонний движок держит большой рефлексивный кеш метаданных. Общий процесс включает эту память. Kora не может удалить память, используемую зависимостью, которую не контролирует. Но её собственные более низкие накладные расходы всё равно оставляют больше бюджета памяти доступным зависимости и бизнес-рабочей нагрузке.

Это одна из причин, почему эффективность фреймворка остаётся полезной, даже когда библиотеки приложения не одинаково лёгкие.


Фреймворк может минимизировать собственные накладные расходы, не контролируя всё

Это ведёт к важному принципу:

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

Это гораздо более реалистичная цель, чем чистота рантайма. Фреймворк контролирует собственные повторяющиеся механизмы. Команда приложения контролирует выбор библиотек. Библиотека контролирует свою внутреннюю архитектуру. Платформа развёртывания добавляет собственные ограничения. Эти слои следует оптимизировать независимо, где возможно.


Чистота этапа компиляции была бы плохой целью

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

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

Результат был бы не просто ограничительным. Он исключал бы большой объём полезного Java-программного обеспечения по причинам, не связанным с корректностью приложения. Команды потеряли бы зрелые SDK, специализированные библиотеки протоколов, системы логирования, драйверы, инструменты тестирования и клиенты вендоров, если бы каждую зависимость не переписали в одобренный Kora стиль. Это был бы тест чистоты экосистемы.

Kora не нуждается в нём.

Преимущество фреймворка в удалении избежимых механизмов рантайма из самого фреймворка, а не в полицейском надзоре за несвязанными библиотеками.


Избирательная работа этапа компиляции — более мощная модель

Реальная модель:

Известная структура, принадлежащая фреймворку
        ↓
генерация на этапе компиляции

Неизвестное или принадлежащее библиотеке поведение
        ↓
обычная модель рантайма библиотеки

Это избирательно.

Например:

Kora знает сигнатуру контроллера
→ генерировать обработчик

Kora знает интерфейс репозитория
→ генерировать реализацию

Kora знает граф зависимостей
→ генерировать связку

Kora не владеет внутренностями OkHttp
→ вызывать OkHttp нормально

Kora не владеет внутренностями SDK вендора
→ вызывать SDK вендора нормально

Это разделение даёт рычаг без требования конформизма экосистемы.


Зрелые внешние библиотеки часто лучше перереализаций фреймворка

Это тесно связано с избеганием NIH.

Предположим, зрелая библиотека уже предоставляет:

  • проверенную в бою реализацию протокола;
  • пулинг соединений;
  • TLS;
  • повторы;
  • сжатие;
  • прокси;
  • аутентификацию;
  • настройку производительности;
  • годы исправлений совместимости.

Перереализация всего этого только ради обеспечения «чистоты этапа компиляции» обычно была бы расточительной.

Лучший фреймворк может обернуть или скомпоновать зрелую библиотеку через тонкую границу.

Интеграции OkHttp и Apache HttpClient Kora демонстрируют ровно этот паттерн.

Слой, специфичный для фреймворка, обрабатывает:

  • конфигурацию;
  • DI;
  • телеметрию;
  • общие контракты клиента Kora.

Базовый транспорт остаётся зрелой независимой библиотекой.


Тонкая интеграционная граница обычно достаточна

Рассмотрим неподдерживаемый SDK вендора.

Интеграция часто может быть:

@ConfigSource("vendor")
public interface VendorConfig {
    String endpoint();

    String token();
}
@ConfigSource("vendor")
interface VendorConfig {
    fun endpoint(): String

    fun token(): String
}

и:

@Module
public interface VendorModule {

    default VendorClient vendorClient(VendorConfig config) {
        return VendorClient.builder()
            .endpoint(config.endpoint())
            .token(config.token())
            .build();
    }
}
@Module
interface VendorModule {

    fun vendorClient(config: VendorConfig): VendorClient {
        return VendorClient.builder()
            .endpoint(config.endpoint())
            .token(config.token())
            .build()
    }
}

Если клиенту нужна очистка:

фабрика
   ↓
обёртка жизненного цикла / AutoCloseable

Если приложению нужна готовность:

клиент
   ↓
ReadinessProbe

Если телеметрии не хватает:

маленькая обёртка
   ↓
OpenTelemetry / метрики

Ни в какой момент SDK не должен становиться «кодом Kora этапа компиляции».


Отражение внутри SDK — его собственная задача

Предположим, SDK вендора внутренне обнаруживает провайдеров учётных данных через отражение.

На HotSpot:

Kora
  ↓
VendorClient
  ↓
обнаружение провайдера на основе отражения

работает как обычная Java, при условии, что сам SDK поддерживает JDK.

Если позже Native Image становится требованием развёртывания, оцените, поставляет ли SDK необходимые метаданные.

Интеграция Kora не обязана концептуально меняться.

Сборке могут понадобиться метаданные.

Это чистое разделение ответственностей.


Динамические прокси внутри библиотеки похожи

Предположим, SDK раскрывает интерфейс и создаёт прокси рантайма внутри:

MyApi api = sdk.create(MyApi.class);
val api: MyApi = sdk.create(MyApi::class.java)

На JVM Kora может внедрять MyApi или фабрику SDK как любой другой объект.

Kora не нужно генерировать прокси сама.

Если приложение использует Native Image, интерфейсы прокси могут нуждаться в регистрации в конфигурации Native Image.

Опять же, это метаданные развёртывания, а не архитектурная несовместимость Kora.


Сканирование аннотаций рантайма внутри библиотеки тоже похоже

Движок валидации, сериализатор, ORM или SDK могут динамически инспектировать аннотации.

Это не конфликтует со сгенерированным графом DI Kora.

Две системы могут сосуществовать:

обработка аннотаций Kora
        ↓
сгенерированный граф

сторонняя библиотека
        ↓
сканирование аннотаций рантайма

Слово аннотация в обоих механизмах не делает их взаимоисключающими.

Они работают в разное время для разных целей.


Стоимость — проблема, только если она действительно проблема

Инженерия должна сопротивляться идеологическим аргументам о производительности.

Отражение может быть медленным относительно прямого вызова.

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

Диспетчеризация прокси, добавляющая наносекунды или микросекунды, может быть нерелевантной, если операция затем выполняет сетевой вызов на 20 мс.

Сканирование метаданных может иметь огромное значение в среде, чувствительной к холодному старту, и быть нерелевантным в долго работающем пакетном сервисе.

Правильный вопрос:

Где находится стоимость?
Как часто она возникает?
На критическом ли она пути?
Имеет ли она значение для SLO?

а не:

Использует ли эта библиотека отражение?

Kora удаляет отражение из собственных общих механизмов фреймворка, потому что эти механизмы появляются повсюду и могут быть разрешены раньше.

Это не значит, что каждое рефлексивное использование в JVM-экосистеме автоматически неприемлемо.


Генерация на этапе компиляции — о повторяющейся работе фреймворка

Внедрение зависимостей — отличный пример.

Если фреймворк уже знает на этапе компиляции:

A зависит от B
B зависит от C

повторное обнаружение этих отношений через отражение при каждом запуске процесса даёт мало ценности для обычного сервиса.

Kora разрешает их рано.

Это высокорычажная оптимизация, потому что построение графа — задача уровня всего фреймворка.

Сторонний SDK, выполняющий маленький рефлексивный поиск для опционального механизма расширения, — другая категория работы.

Не всё отражение имеет одинаковую архитектурную значимость.


AOP похож

Kora генерирует AOP-подклассы, потому что перехват методов — механизм, принадлежащий фреймворку, и его можно сконструировать заранее.

Сторонний клиент всё равно может использовать прокси внутри.

Эти факты могут сосуществовать:

Сервис приложения
   ↓
сгенерированный AOP-подкласс Kora
   ↓
интерфейс SDK
   ↓
динамический прокси SDK

Только граница вызова SDK использует свой прокси.

Собственное аспектное поведение Kora остаётся сгенерированным и инспектируемым.


Сериализация тоже может быть смешанной

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

Это технически непримечательно.

Например:

публичный API
  ↓
сгенерированный Kora JSON-маппинг

SDK вендора
  ↓
отражение Jackson

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

Опять же, архитектуре фреймворка не нужно навязывать глобальное единообразие.


Библиотеки тестирования — очевидный пример

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

Правило, запрещающее эти механизмы на уровне всего приложения, сделало бы обычное JVM-тестирование ненужно трудным.

Тесты Kora всё равно работают внутри обычной Java-экосистемы.

Mockito, расширения JUnit, Testcontainers, инструменты инструментации байткода, профилирующие агенты и тестовые фикстуры могут иметь поведение рантайма, полностью отличное от производственной модели DI Kora.

Это ожидаемо.


Инструменты разработки — ещё один слой

То же приложение может использовать:

  • Java-агенты;
  • профилировщики;
  • инструментацию отладчика;
  • инструментацию покрытия;
  • инструменты горячей перезагрузки;
  • JFR;
  • прокси, сгенерированные IDE;
  • трансформации тестового рантайма.

Kora не требует, чтобы эти инструменты разделяли её стратегию этапа компиляции.

Это иллюстрирует, почему фразу фреймворк с этапом компиляции следует тщательно ограничивать.

Она описывает архитектуру фреймворка, а не метафизику процесса.


JVM остаётся платформой

Это самый важный концептуальный якорь.

Kora не заменяет JVM.

Она работает на ней.

Поэтому обычная Java-экосистема остаётся доступной:

загрузка классов
отражение
ServiceLoader
method handles
прокси
JNI
агенты
стандартные библиотеки
сторонние библиотеки

Kora просто старается не зависеть от более динамичных механизмов, когда может сгенерировать более простой прямой код для задач, принадлежащих фреймворку.

Это стратегия производительности и прозрачности.

А не ограничение совместимости.


Native Image меняет допущения платформы

При использовании Native Image среда исполнения меняется.

Полученный бинарник всё ещё строится из Java-кода, но допущения другие, потому что программа анализируется заранее.

Динамическое поведение, которое HotSpot обнаруживает естественно, может нуждаться в явном описании.

Вот почему документация Native Image Kora обсуждает:

  • метаданные отражения;
  • метаданные ресурсов;
  • метаданные прокси;
  • метаданные сериализации;
  • метаданные JNI;
  • инициализацию классов.

Эти требования исходят из анализа закрытого мира Native Image.

Поэтому одна и та же библиотека может иметь два профиля совместимости:

HotSpot:
работает напрямую

Native Image:
работает с метаданными

или:

HotSpot:
работает напрямую

Native Image:
не поддерживается из-за фундаментального динамического поведения

Это не меняет того, пригодна ли библиотека для использования в JVM-сервисе на Kora.


Совместимость с Native Image должна быть по-зависимостной

Если Native Image требуется, относитесь к совместимости как к инвентаризации зависимостей.

Для каждой важной библиотеки спросите:

Официально ли она поддерживает Native Image?
Поставляет ли метаданные достижимости?
Покрыта ли она репозиторием метаданных?
Использует ли динамическую загрузку классов?
Генерирует ли байткод?
Нужен ли ей JNI?
Использует ли ресурсы динамически?

Это та же должная осмотрительность, которую требуют другие Java-стеки, нацеленные на Native Image.

Преимущество Kora в том, что сам фреймворк вводит относительно мало дополнительных сюрпризов.


Более чистая база важна

Рассмотрим два приложения с одним и тем же сторонним SDK.

Приложение A использует фреймворк с тяжёлым рантаймом, который сам нуждается в обширном отражении, прокси, сканировании рантайма и специфичных для фреймворка нативных подсказках.

Приложение B использует Kora, чьи граф, аспекты, репозитории и HTTP-адаптеры уже сгенерированы.

Обоим могут понадобиться метаданные для SDK.

Но приложение B начинает с:

метаданные стороннего SDK

а не:

метаданные фреймворка
+
метаданные прокси фреймворка
+
метаданные обнаружения фреймворка
+
метаданные стороннего SDK

Точная реальная разница зависит от задействованных фреймворков и модулей, но архитектурная база чище.

Это честное заявление о Native Image.


«Нет отражения в Kora» следует читать точно

Технически точная интерпретация:

Обычный рантайм фреймворка Kora не полагается на отражение для собственного графа зависимостей и сгенерированных механизмов фреймворка.

Это отличается от:

Никакой байткод в процессе Kora не может вызывать Reflection API.

Первое утверждение — архитектурное свойство.

Второе было бы ограничением платформы.

Kora делает первое утверждение.


«Нет динамических прокси» тоже следует читать точно

Аналогично:

Kora не использует динамические прокси для своей модели AOP/DI фреймворка

не значит:

Proxy.newProxyInstance нелегален в процессе Kora

Если зависимость использует динамические прокси, JVM будет их выполнять.

Собственный граф приложения Kora не становится прокси-основанным из-за этого.

Опять же, важна область действия.


«Нет генерации байткода рантайма» тоже ограничено

Kora избегает генерации байткода рантайма для собственных механизмов фреймворка.

Но если библиотека использует Byte Buddy во время выполнения на HotSpot, Kora не отключает Byte Buddy.

Поддержка библиотекой Native Image — другой вопрос.

Правильное прочтение всегда:

стратегия реализации Kora

а не:

глобальное ограничение процесса

Что на самом деле сделало бы библиотеку трудной для использования с Kora?

Библиотека всё же может быть неудобной для Kora, но обычно по обычным архитектурным причинам.

Например, она может:

  • требовать глобальный статический синглтон;
  • предполагать, что владеет запуском процесса;
  • устанавливать несовместимый event-loop;
  • требовать специфичную модель привязки потоков;
  • полностью скрывать жизненный цикл;
  • опираться на специфичный для фреймворка контейнер;
  • требовать наследования от несовместимого базового класса приложения;
  • требовать плетения байткода по классам приложения;
  • ожидать сервлетные API, когда приложение не сервлетное.

Это реальные несоответствия интеграции.

Заметьте, что они не сводятся просто к:

использует отражение

Рефлексивная библиотека с обычным конструктором может интегрироваться легче, чем библиотека без отражения, но с инвазивной моделью жизненного цикла.

Совместимость архитектурна, а не идеологична.


Модель потоков важнее отражения во многих случаях

Kora 2 исполняет обычный код приложения синхронно на виртуальных потоках.

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

Например:

  • Потокобезопасен ли клиент?
  • Блокируется ли он?
  • Требует ли он один event-loop?
  • Раскрывает ли колбэки на собственном исполнителе?
  • Некорректно ли сохраняет thread-local состояние?
  • Закрепляет ли носители через нативные вызовы или синхронизированные секции?
  • Есть ли у него собственный большой пул потоков?

Эти факторы могут материально влиять на сервис.

Одноразовый рефлексивный поиск конструктора может и не влиять.

Вот почему совместимость нужно оценивать в инженерных терминах, а не по ярлыкам механизмов.


Жизненный цикл тоже важен

Если библиотека создаёт:

  • сокеты;
  • фоновые потоки;
  • исполнители;
  • пулы;
  • наблюдателей файлов;
  • нативные дескрипторы;

важный вопрос интеграции Kora — как эти ресурсы входят в жизненный цикл приложения и покидают его.

Часто решение — маленькая фабрика:

конфигурация библиотеки
     ↓
фабрика клиента
     ↓
клиент
     ↓
жизненный цикл
     ↓
граф

Отражение внутри библиотеки ортогонально.


Конфигурация обычно проста

Сторонний SDK можно настроить через интерфейс конфигурации Kora, а затем сконструировать с его нативным билдером.

Например:

@ConfigSource("search")
public interface SearchConfig {
    String endpoint();

    Duration timeout();
}
@ConfigSource("search")
interface SearchConfig {
    fun endpoint(): String

    fun timeout(): Duration
}

затем:

@Module
public interface SearchModule {

    default SearchClient searchClient(SearchConfig config) {
        return SearchClient.builder()
            .endpoint(config.endpoint())
            .timeout(config.timeout())
            .build();
    }
}
@Module
interface SearchModule {

    fun searchClient(config: SearchConfig): SearchClient {
        return SearchClient.builder()
            .endpoint(config.endpoint())
            .timeout(config.timeout())
            .build()
    }
}

SDK может использовать отражение внутри.

Kora не заботится, если только модель развёртывания или требования производительности не делают это релевантным.


Телеметрию можно добавлять вне библиотеки

Библиотеке не нужна нативная поддержка Kora, чтобы участвовать в наблюдаемости.

Варианты включают:

  • использование собственной интеграции OpenTelemetry библиотеки;
  • прикрепление метрик Micrometer, если она их поддерживает;
  • обёртывание важных операций;
  • добавление перехватчика;
  • раскрытие маленького адаптера телеметрии.

Этого часто достаточно, чтобы превратить обычную библиотеку в производственный компонент Kora.

Мост архитектуры не требуется.


Пробы можно добавлять независимо

То же самое применимо к готовности.

Если клиент раскрывает состояние здоровья, создайте ReadinessProbe.

Если нет, решите, должно ли здоровье внешней зависимости вообще влиять на готовность.

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

Операционная интеграция и стратегия реализации — отдельные задачи.


Реальная интеграция часто меньше, чем люди ожидают

Фраза «Kora не поддерживает библиотеку X» может звучать как серьёзный пробел совместимости.

На практике интеграция может быть:

XConfig
XModule
XReadinessProbe
XTelemetry

а код приложения может внедрять нативный XClient.

Различие между «официальной интеграцией» и «пригодной для использования библиотекой» не следует путать.

Официальная интеграция даёт удобство и стандартизированное производственное поведение.

Пригодность к использованию часто требует гораздо меньше.


Kora даёт архитектуру по умолчанию, а не тест чистоты экосистемы

Это лучший способ резюмировать модель расширения:

Kora даёт вам архитектуру по умолчанию, а не тест чистоты экосистемы.

Архитектура по умолчанию говорит:

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

Она не говорит:

  • каждая зависимость должна быть авторства Kora;
  • каждая библиотека должна избегать отражения;
  • каждая библиотека должна поддерживать генерацию кода;
  • каждая библиотека должна быть готова к Native Image;
  • каждая библиотека должна раскрывать специфичные для Kora API.

Это подрывало бы саму идею JVM-экосистемы.


Механика, принадлежащая фреймворку, и внешняя технология должны обрабатываться по-разному

Полезное правило принятия решений:

Владеет ли Kora этим механизмом?
        ↓ да
Можно ли его разрешить на этапе компиляции?
        ↓ да
Генерируйте прямой код.

Владеет ли внешняя библиотека механизмом?
        ↓
Используйте её обычный Java API.

Это избегает ненужного дублирования.

Это также держит фреймворк маленьким.


Вот почему Kora может быть и сфокусированной, и открытой

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

Kora действительно сфокусирована на собственных внутренностях.

Но это не делает её закрытой для Java-экосистемы.

На самом деле тонкие абстракции могут делать фреймворк более открытым, потому что меньше внешних библиотек нужно переводить в проприетарную модель фреймворка.

Графу нужен только Java-объект.

Как этот объект реализует себя — в значительной степени его собственная забота.


Граф зависимостей не заботится о том, как объект работает внутри

Этот момент почти до неловкости прост, но он проясняет всю тему.

Предположим, граф содержит:

OrderService
    ↓
FraudSdk

Kora нужно знать, как получить FraudSdk.

Как только объект существует, графу не нужно понимать, использует ли FraudSdk внутри:

отражение
динамические прокси
ServiceLoader
method handles
Netty
JNI
фоновый поток
сгенерированный байткод

Это детали реализации компонента.

Внедрение зависимостей — об отношениях объектов, а не о философском конформизме.


DI этапа компиляции и динамические внутренности естественно сосуществуют

Поэтому полная форма может быть:

граф этапа компиляции

OrderService
      ↓
FraudClient
      ↓
фабрика, известная на этапе компиляции

внутренности рантайма FraudClient

FraudClient
      ↓
динамический поиск провайдера
      ↓
отражение
      ↓
прокси

Граф статически валидирован.

Компонент динамически реализован.

Здесь нет противоречия.


Native Image — где граница становится видимой

Когда приложение переходит с HotSpot на Native Image, вторая половина этой диаграммы важна для AOT-компилятора.

Граф Kora уже видим.

Внутренности FraudClient могут быть нет.

Вот когда могут потребоваться метаданные достижимости или адаптация, специфичная для библиотеки.

Именно поэтому совместимость с Kora и совместимость с Native Image нужно оценивать отдельно.


Когда библиотека действительно враждебна Native Image

Некоторые библиотеки фундаментально плохо подходят для Native Image.

Примеры могут включать библиотеки, зависящие от:

  • загрузки произвольных JAR-плагинов;
  • вызова компилятора рантайма;
  • генерации классов рантайма для неизвестных схем;
  • агентов, переопределяющих классы;
  • неподдерживаемого поведения JNI.

Если Native Image обязателен, команде может понадобиться:

  • другая библиотека;
  • другой режим исполнения;
  • граница sidecar/сервиса;
  • развёртывание на JVM вместо нативного.

Это решение архитектуры приложения.

Это не свидетельство того, что Kora не может использовать обычные Java-библиотеки.


HotSpot — всё ещё первоклассный выбор развёртывания

JVM остаётся чрезвычайно способной.

JIT-компиляция, динамическая загрузка классов, зрелая наблюдаемость, профилировщики, агенты, адаптивная оптимизация и широкая совместимость библиотек — главные сильные стороны.

Kora не отказывается от них только потому, что её собственные механизмы фреймворка генерируются на этапе компиляции.

Команда может использовать Kora именно потому, что хочет:

обычную JVM
+
более низкие накладные расходы фреймворка
+
граф этапа компиляции
+
виртуальные потоки
+
нативную экосистему

Native Image опционален.

Это важная архитектурная свобода.


Аргумент производительности рантайма Kora переживает сторонние библиотеки

Предположим, сервис использует десять библиотек, две из которых относительно динамичны.

Kora всё равно удаляет собственные повторяющиеся накладные расходы из:

  • внедрения зависимостей;
  • HTTP-маппинга;
  • генерации репозиториев;
  • диспетчеризации аспектов;
  • маппинга конфигурации;
  • сериализации, где используется сгенерированный маппинг.

Эти экономии остаются реальными.

Общее приложение может быть быстрее или не быстрее другого стека в зависимости от рабочей нагрузки.

Но одна динамическая зависимость не аннулирует архитектуру фреймворка.

Так не работают аддитивные стоимости.


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

Лучшая модель производительности:

путь запроса A
=
маршрут Kora
+ бизнес-логика
+ репозиторий JDBC

путь запроса B
=
маршрут Kora
+ бизнес-логика
+ SDK с тяжёлым отражением
+ сетевой вызов

путь запроса C
=
потребитель Kafka
+ сгенерированный маппинг
+ бизнес-логика

Только путь B платит за SDK.

Это гораздо точнее, чем говорить:

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

Думайте и о частоте

Даже внутри пути B частота важна.

Если отражение используется один раз для инициализации кеша метаданных:

запуск:
отражение

установившийся режим:
прямые вызовы

стоимость рантайма может быть пренебрежимой.

Если отражение происходит на каждое поле на каждый запрос, оно может иметь большее значение.

Ответить может только измерение.

Ярлыки механизмов — не профили производительности.


У Native Image тот же принцип

Для Native Image важно не морально то, использует ли зависимость отражение.

Важно, можно ли определить и зарегистрировать рефлексивные цели.

Библиотека со значительным отражением может работать отлично, если её достижимые классы описываемы.

Библиотека с малым отражением всё равно может падать, если динамически загружает неизвестные реализации.

Детали реализации важнее ярлыка.


Метаданные достижимости локализуют проблему

Одно из лучших свойств метаданных Native Image — локальность.

Если зависимости X нужны:

reflect-config
proxy-config
resource-config

эти объявления часто могут жить с X или её интеграцией.

Остальному приложению не нужно становиться осведомлённым об отражении.

Это ведёт к полезной форме:

приложение Kora
├── фреймворк: уже дружелюбен к AOT
├── библиотека A: метаданные не нужны
├── библиотека B: поставляет метаданные
└── библиотека C: требуются кастомные метаданные

Проблема специфична для зависимости.


Это лучше, чем накопление подсказок по всему фреймворку

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

Kora снижает объём принадлежащего фреймворку динамического поведения, которое нужно описывать.

Это делает оставшиеся нативные проблемы легче изолируемыми.

Поверхность проблемы смещается к:

Какая зависимость динамична?

вместо:

Какая подсистема фреймворка сгенерировала скрытое динамическое поведение?

Это настоящее преимущество поддерживаемости.


Руководство фреймворка по Native Image отражает эту философию

Поддержка Native Image в Kora документирует:

  • метаданные, встроенные в модули;
  • сторонние метаданные;
  • репозиторий метаданных достижимости;
  • кастомные метаданные;
  • генерацию трассировочным агентом;
  • подсказки отражения.

Это не документация фреймворка, притворяющегося, что отражения не существует.

Это документация фреймворка, который удаляет отражение там, где владеет кодом, и признаёт его там, где его используют зависимости.

Это правильный уровень прагматизма.


Метаданные рантайма не обязательно плохой дизайн

Есть ещё одна философская ошибка, которой стоит избегать.

Генерация на этапе компиляции часто отлична, когда информация статична.

Но некоторые системы по-настоящему динамичны.

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

Сериализатор может намеренно инспектировать неизвестные пользовательские классы.

Скриптовый движок может намеренно загружать неизвестный код.

Фреймворк внедрения зависимостей для хоста плагинов может намеренно поддерживать изменяемые определения.

Отражение иногда — корректный механизм.

Дизайну Kora не нужно объявлять эти системы архитектурно неверными.

Он просто выбирает другой компромисс для собственной модели производственного бэкенд-фреймворка.


Лучшая архитектура использует статические знания, когда они существуют

Более сильный принцип:

Если что-то известно на этапе компиляции и дорого или непрозрачно заново обнаруживать во время выполнения, используйте знания этапа компиляции.

Kora применяет это к:

графу зависимостей
сигнатурам контроллеров
объявлениям репозиториев
аспектным аннотациям
маппингам

Но если информация действительно динамична или принадлежит третьей стороне, нет выгоды притворяться, что она статична.

Это различие избегает идеологии.


Техники этапа компиляции и рантайма могут сосуществовать в одном сервисе

Современный бэкенд может сочетать всё это:

DI этапа компиляции Kora
сгенерированные репозитории Kora
сгенерированные HTTP-обработчики Kora
отражение Jackson в одном SDK вендора
ServiceLoader в драйвере
динамический прокси в другом SDK
JNI в библиотеке сжатия
обновление конфигурации рантайма
метаданные GraalVM для нативной сборки

Это не архитектурная несогласованность.

Это композиция программного обеспечения.

Каждый слой решает другую проблему.


JVM-библиотекам не нужна «сертификация Kora»

Нет общего требования, чтобы библиотека была явно спроектирована для Kora, прежде чем её можно будет использовать.

Если библиотека раскрывает обычные Java-конструкторы, билдеры, фабрики или статические методы создания, её обычно можно поместить в граф.

Это делает практическую поверхность совместимости гораздо больше, чем список официальных модулей Kora.

Официальные модули улучшают эргономику.

Они не граница экосистемы.


Официальный модуль против прямого использования библиотеки

Официальная интеграция Kora обычно добавляет такие вещи, как:

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

Прямое использование библиотеки может требовать, чтобы приложение предоставляло эти части само.

Разница — в удобстве и качестве производственной интеграции.

Это не обязательно базовая совместимость.


Это важно для критики маленькой экосистемы

Распространённая критика меньших фреймворков:

Что если ровно нужная мне библиотека не имеет интеграции Kora?

Ответ не автоматически:

вы не можете её использовать

Более точный ответ:

используйте нативную библиотеку
+
раскройте её через модуль Kora
+
добавьте жизненный цикл/конфигурацию/телеметрию, если необходимо

Тот факт, что библиотека использует отражение внутри, не меняет этот базовый путь.

Вот почему расширяемость фреймворка важнее сырого количества стартеров.


Конкретный паттерн интеграции

Представьте аналитический SDK без официального модуля Kora.

Нативная библиотека раскрывает:

AnalyticsClient.builder()
    .endpoint(...)
    .apiKey(...)
    .build();
AnalyticsClient.builder()
    .endpoint(...)
    .apiKey(...)
    .build()

Интеграция Kora может быть:

AnalyticsConfig
       ↓
фабрика AnalyticsClient
       ↓
жизненный цикл, если нужно
       ↓
обёртка телеметрии, если нужно
       ↓
сервис приложения

Если SDK внутренне сканирует аннотации или использует прокси, HotSpot исполняет его нормально.

Если позже требуется Native Image, проверьте поддержку Native Image в SDK.

Архитектура на стороне Kora остаётся почти неизменной.


«Мост» нужен только при реальном семантическом несоответствии

Иногда реальный адаптер необходим.

Например:

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

Это семантический мост.

Но он не вызван отражением.

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

Рефлексивная библиотека может не нуждаться ни в каком.

Это ещё одна причина, почему утверждения о совместимости на основе механизмов вводят в заблуждение.


Совместимость с Native Image можно улучшать инкрементально

Команде не нужно решать все задачи Native Image в первый же день.

Разумный путь может быть:

1. Построить приложение Kora на HotSpot.
2. Валидировать производственную архитектуру.
3. Решить, создаёт ли Native Image достаточно ценности.
4. Запустить нативную сборку.
5. Определить неподдерживаемые зависимости.
6. добавить метаданные / обновить / заменить, где оправдано.

Это предотвращает преждевременное ограничение выбора библиотек нативной совместимостью, когда развёртывание может никогда её не потребовать.


Native Image должен быть явным нефункциональным требованием

Если проект обязан поставляться как нативный исполняемый файл, поместите это требование рядом с:

  • версией JDK;
  • операционной системой;
  • архитектурой CPU;
  • SLO задержки;
  • целевым объёмом памяти;
  • целевым временем запуска.

Затем оценивайте зависимости соответственно.

Не выводите это просто из выбора Kora.

Это держит архитектурные требования честными.


Kora делает нативное требование легче, а не автоматическим

Честное утверждение:

архитектура Kora
→ снижает трение фреймворк-native-image

а не:

Kora
→ каждая Java-зависимость автоматически работает в Native Image

Ни один фреймворк не может заставить произвольные внутренности библиотек исчезнуть.

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

Именно это делает Kora.


Граница Native Image также полезна для отладки

Руководство Kora по Native Image предлагает изолировать подозрительные зависимости минимальными пробами.

Эта модель отладки отражает то же разделение.

Если библиотека X падает в минимальном нативном исполняемом файле без Kora, проблема принадлежит библиотеке X или её метаданным.

Это мощная диагностическая техника, потому что предотвращает приписывание каждой нативной ошибки фреймворку.

Изоляция проясняет владение.


Обычное исполнение JVM остаётся точкой отсчёта

Когда приложение на Kora работает на обычной JVM, но падает как Native Image, это наблюдение уже сужает проблему.

Граф приложения не вдруг становится невалидным.

Обычная Java-семантика не обязательно неверна.

Вероятное различие лежит в:

  • достижимости;
  • тайминге инициализации;
  • ресурсах;
  • метаданных прокси;
  • JNI;
  • неподдерживаемой генерации рантайма.

Это делает отладку, специфичную для развёртывания, гораздо более дисциплинированной.


Производительность отражения следует измерять, а не бояться

Маленькая рефлексивная библиотека может быть полностью нерелевантна производительности сервиса.

Большой сгенерированный фреймворк всё равно может быть медленным по другим причинам.

Единственный надёжный метод — измерение.

Для кода приложения профилируйте:

  • CPU;
  • аллокации;
  • конкуренцию за блокировки;
  • запуск;
  • загрузку классов;
  • сетевое время;
  • время базы данных.

Если рефлексивные вызовы доминируют, оптимизируйте их.

Если доминирует база данных, отражение — отвлечение.

Модель этапа компиляции Kora ценна, потому что удаляет категорию накладных расходов фреймворка до начала профилирования.

Это делает оставшийся профиль более сформированным под приложение.


Это лучшее определение прозрачности

Прозрачность не значит, что каждая строка каждой зависимости следует одному архитектурному стилю.

Это значит, что фреймворк не вводит скрытое поведение без необходимости.

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

сгенерированный граф Kora
      ↓
нативный клиент

Если клиент медленный, инспектируйте клиент.

Если граф неверен, компилятор сообщает об этом.

Ответственности разделены.


Этап компиляции — инструмент, а не идеология

Это более широкий философский вывод.

Генерация на этапе компиляции полезна, потому что некоторая информация уже доступна и может быть превращена в прямой код рано.

Она не морально превосходна.

Отражение рантайма полезно, потому что некоторая информация доступна или удобно выражается только динамически.

Оно не по своей природе плохо.

Динамические прокси полезны для определённых API.

Обнаружение рантайма полезно для систем плагинов.

У Native Image другие ограничения, потому что он оптимизирует под другую модель исполнения.

Хороший фреймворк использует правильный механизм для слоя, которым владеет.

Ценность Kora в дисциплинированном выборе, а не абсолютистском.


Фактическая философия Kora избирательна

Краткая формулировка:

Где Kora владеет абстракцией:
    переместите ненужные механизмы рантайма на этап компиляции.

Где зрелые внешние библиотеки владеют технологией:
    используйте их обычные Java API.

Где требуется Native Image:
    оценивайте динамическое поведение и метаданные явно.

Эта модель и производительна, и дружелюбна к экосистеме.


Неправильная ментальная модель

Неправильная модель:

Kora
  ↓
всё в процессе
  ↓
должно генерироваться на этапе компиляции

Это делает Kora похожей на ограниченный рантайм языка.

Это не так.


Лучшая ментальная модель

Лучшая модель:

JVM-приложение
│
├── механика, принадлежащая Kora
│      └── в основном сгенерирована на этапе компиляции
│
├── код приложения
│      └── обычная Java/Kotlin
│
├── сторонние библиотеки
│      └── используют те JVM-механизмы, которые используют
│
└── выбор развёртывания
       ├── HotSpot
       └── Native Image

Затем Native Image добавляет ещё один слой анализа:

Если Native Image:
    оценивайте динамическое поведение библиотек
    и предоставляйте метаданные, где требуется.

Это точно разделяет задачи.


Матрица решений

Практическая матрица совместимости выглядит так:

Характеристика библиотеки Kora на HotSpot Kora на Native Image
Обычный Java-клиент Обычно прямолинейно Обычно прямолинейно
Использует отражение Обычно нормально Могут понадобиться метаданные достижимости
Использует динамические прокси Обычно нормально Могут потребоваться метаданные прокси
Использует ServiceLoader Обычно нормально Провайдеры/ресурсы могут нуждаться в регистрации
Динамически загружает ресурсы Обычно нормально Ресурсы могут нуждаться во включении
Использует генерацию байткода рантайма Обычно нормально, если библиотека поддерживает JDK Может быть трудно или неподдерживаемо
Использует JNI Зависит от платформы/библиотеки Может потребоваться дополнительная работа native-image/JNI
Загружает неизвестные JAR-плагины Обычная возможность JVM Часто плохо подходит для native-image закрытого мира
Поставляет метаданные GraalVM Нормально Обычно гораздо легче
Покрыта репозиторием метаданных Нормально Обычно легче

Заметьте, что левая колонка — не «не поддерживается».

В этом вся суть.


Практический рабочий процесс оценки

Рассматривая новую библиотеку для Kora, используйте эту последовательность.

Во-первых, игнорируйте Native Image и оценивайте библиотеку как Java-программное обеспечение.

Хорошо ли она решает проблему?

Поддерживается ли она?

Хорош ли API?

Соответствует ли она требованиям производительности и безопасности?

Во-вторых, интегрируйте её через граф.

Создавайте типизированную конфигурацию, фабрику, жизненный цикл, телеметрию и пробы только по мере необходимости.

В-третьих, профилируйте, если производительность важна.

Не отвергайте библиотеку только потому, что её внутренности динамичны.

В-четвёртых, если Native Image — требование развёртывания, оценивайте её метаданные и неподдерживаемые функции.

Этот порядок предотвращает превращение трёх отдельных инженерных задач в один расплывчатый миф о совместимости фреймворка.


Почему миф сохраняется

Миф понятен, потому что фреймворки с этапом компиляции часто маркетируются вместе с Native Image.

Концепции усиливают друг друга:

DI этапа компиляции
+
нет отражения
+
native image
+
быстрый запуск

Со временем люди сжимают это в:

фреймворк с этапом компиляции
=
отражение запрещено

Но фактическое причинно-следственное отношение уже.

Архитектура этапа компиляции Kora делает Native Image легче.

У Native Image более сильные ограничения, чем у HotSpot.

Эти ограничения не текут назад и не становятся правилами JVM-развёртывания.


Фреймворк-нативная дружелюбность отличается от приложение-нативной дружелюбности

Фреймворк может быть высоко дружелюбен к Native Image, в то время как приложение — нет.

Например:

Фреймворк Kora:
отличная нативная совместимость

приложение:
использует скриптовый движок рантайма
загружает произвольные плагины
использует неподдерживаемую JNI-библиотеку

Конечное приложение может быть плохим кандидатом для Native Image.

Это не подрывает нативную архитектуру Kora.

Это просто значит, что зависимости приложения определяют конечный конверт совместимости.


Приложение-нативная дружелюбность — пересечение зависимостей

Лучшая модель:

Совместимость с Native Image
=
совместимость Kora
∩ совместимость драйвера базы данных
∩ совместимость HTTP-клиента
∩ совместимость логирования
∩ совместимость SDK
∩ поведение приложения
∩ нативные зависимости

Самая слабая зависимость может определять результат.

Kora контролирует лишь одну часть этого уравнения.

Её преимущество в том, что она делает свою часть относительно сильной.


Это верно для каждого Java-фреймворка

Ни один Java-фреймворк не может гарантировать совместимость с Native Image для произвольных зависимостей приложения.

Даже если сам фреймворк выполняет идеальный AOT-анализ, приложение может добавить:

  • кастомный агент;
  • проприетарную нативную библиотеку;
  • систему плагинов с тяжёлым отражением;
  • неподдерживаемый драйвер.

Фреймворк может предоставлять подсказки, метаданные и интеграции.

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

Поэтому оценка на уровне зависимостей неизбежна.


Чистая база Kora всё равно ценна

Тот факт, что Native Image — экосистемная задача, не снижает преимущество Kora.

Он его проясняет.

Kora избегает добавления большого специфичного для фреймворка динамического слоя поверх сторонних зависимостей.

Когда появляется проблема, команды чаще могут локализовать её в:

зависимость X

вместо:

внутренности фреймворка + зависимость X

Это операционно полезно.


Библиотека может даже намеренно использовать отражение для расширяемости

Рассмотрим библиотеку, которая обнаруживает кодеки из аннотаций во время выполнения.

Это может быть намеренным дизайнерским выбором.

На HotSpot использование её внутри Kora может быть вполне разумным.

Если нативная компиляция важна, команда может предпочесть сгенерированный маппер Kora.

Суть в том, что выбор может быть локальным.

Одна часть приложения может использовать маппинг этапа компиляции.

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

Kora не требует архитектурной монокультуры.


Смешанные стратегии часто практический оптимум

Реальные приложения неоднородны.

Сервис может использовать:

сгенерированный DI Kora
сгенерированный JSON Kora
генерацию репозиториев JDBC
транспорт OkHttp
Logback
рефлексивный SDK вендора
JNI-сжатие
драйвер базы данных на основе ServiceLoader

Это может быть ровно корректная архитектура.

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

Дизайн фреймворка должен допускать композицию.


Это также причина, почему тонкие абстракции важны

Если бы Kora оборачивала каждую стороннюю библиотеку за толстой проприетарной моделью, несовместимости было бы труднее изолировать.

С тонкой интеграцией:

Kora
  ↓
нативный клиент

разработчик может обращаться к документации нативной библиотеки и поддержке Native Image напрямую.

Меньше семантической дистанции.

Это улучшает и обычную JVM-интеграцию, и устранение неполадок native-image.


Базовая экосистема остаётся пригодной

Приложение на Kora может выигрывать от зрелости Java-экосистемы:

  • десятилетий драйверов баз данных;
  • зрелых HTTP-клиентов;
  • облачных SDK;
  • библиотек безопасности;
  • библиотек наблюдаемости;
  • парсеров;
  • стеков протоколов;
  • форматов данных;
  • нативных интеграций.

Kora не нуждается в нативных заменах этапа компиляции для всех них.

Это важная причина, почему фреймворк может оставаться относительно маленьким.


Корректное утверждение о совместимости

Вместо того чтобы говорить:

Kora работает только с библиотеками этапа компиляции.

скажите:

Собственные механизмы фреймворка Kora спроектированы вокруг генерации на этапе компиляции, в то время как сторонние Java-библиотеки сохраняют свою обычную модель рантайма.

Вместо того чтобы говорить:

Библиотеки с тяжёлым отражением несовместимы с Kora.

скажите:

Библиотеки с тяжёлым отражением обычно работают нормально на JVM; если требуется Native Image, их требования достижимости нужно оценивать.

Вместо того чтобы говорить:

Одна рефлексивная зависимость уничтожает производительность Kora.

скажите:

Зависимость добавляет собственную стоимость на путях кода, где выполняется; собственный сгенерированный рантайм Kora остаётся неизменным.

Этот словарь гораздо точнее.


Сильнейший пример: Kora сама документирует метаданные отражения

Пожалуй, самое ясное свидетельство — что собственная документация Native Image Kora явно объясняет, как обрабатываются метаданные отражения третьих сторон.

Это имеет смысл только если архитектура принимает возможность, что внешние зависимости используют отражение.

Документация описывает метаданные для рефлексивных конструкторов, динамических прокси, ресурсов, сериализации, JNI и сторонних библиотек.

Это не лазейка.

Это ожидаемая модель интероперабельности.


Фреймворк с этапом компиляции и экосистема рантайма дополняют друг друга

Более точная архитектурная история:

Kora:
компилировать известную структуру фреймворка рано

JVM-экосистема:
сохранять зрелые библиотеки рантайма

GraalVM:
добавлять AOT-ограничения только когда выбрано нативное развёртывание

Эти слои дополняют друг друга.

Kora получает производительность и прозрачность, не отказываясь от экосистемы.

JVM-экосистема сохраняет динамические возможности там, где они нужны библиотекам.

Native Image остаётся опциональной оптимизацией развёртывания с явными требованиями совместимости.


Что Kora на самом деле вам даёт

Модель этапа компиляции Kora даёт вам:

  • валидацию графа зависимостей до рантайма;
  • сгенерированную связку;
  • сгенерированные аспекты вместо AOP-прокси рантайма;
  • сгенерированные реализации репозиториев;
  • сгенерированные HTTP-адаптеры;
  • сгенерированные маппинги;
  • меньше обнаружения фреймворка при запуске;
  • меньше метаданных фреймворка рантайма;
  • более чистую базу Native Image;
  • инспектируемый сгенерированный исходный код.

Она не даёт вам:

  • автоматическую нативную совместимость для произвольных библиотек;
  • гарантию, что каждая зависимость быстрая;
  • устранение всего отражения в процессе;
  • устранение всех динамических прокси в процессе;
  • свободу от проблем JNI или загрузки классов во внешнем ПО.

Понимание этой границы делает преимущества более достоверными, а не менее.


Что сторонние библиотеки всё ещё решают

Каждая зависимость всё ещё контролирует:

  • свою внутреннюю объектную модель;
  • потоки;
  • загрузку классов;
  • отражение;
  • генерацию прокси;
  • сериализацию;
  • загрузку ресурсов;
  • JNI;
  • использование памяти;
  • поведение инициализации.

Kora интегрирует полученный объект в граф.

Это обычная композиция программного обеспечения.


Что развёртывание всё ещё решает

Среда развёртывания контролирует ещё один слой:

HotSpot
OpenJDK
режим JVM GraalVM
Native Image
ограничения контейнера
ОС
архитектура CPU

Библиотека может вести себя по-разному в этих средах.

Выбор фреймворка и выбор развёртывания взаимодействуют, но не взаимозаменяемы.


Не отвергайте библиотеку по неверной причине

Если библиотека — плохой выбор, потому что:

  • она не поддерживается;
  • она чрезмерно аллоцирует;
  • она запускает сотни потоков;
  • у неё проблемы безопасности;
  • она слишком медленная;
  • у неё плохая семантика сбоев;
  • она не поддерживает целевую версию JDK;

отвергните её.

Если Native Image обязателен и библиотека фундаментально не может его поддерживать, отвергните или изолируйте её.

Но отвергать полезную JVM-библиотеку только потому, что она использует отражение внутри, когда приложение будет работать на HotSpot, — не требование Kora.

Это была бы самонавязанная чистота архитектуры.


Не преувеличивайте и Native Image

Native Image может давать значительные преимущества запуска и занимаемой площади.

Но он также меняет сложность сборки, профилирование, отладку, совместимость и поведение оптимизации.

Команда должна выбирать его, потому что экономика развёртывания это оправдывает.

Kora делает опцию более доступной.

Она не превращает её в обязательный пункт назначения.

Это особенно важно для долго работающих сервисов, где JIT HotSpot и широкая совместимость экосистемы могут оставаться отличными компромиссами.


Kora на HotSpot — всё ещё полностью Kora

Сервис на Kora не становится менее аутентичным, потому что работает как обычный JVM-байткод.

Основная архитектура остаётся:

граф этапа компиляции
сгенерированный код фреймворка
тонкие абстракции
виртуальные потоки
нативная Java-экосистема

Это уже центральное ценностное предложение фреймворка.

Native Image — дополнительный режим развёртывания.


Лучшая ментальная модель — слоистая свобода

Kora сфокусирована там, где мнение создаёт сильный рычаг:

DI
AOP
маппинг
репозитории
HTTP-контракты

Она открыта там, где важна разнообразие экосистемы:

библиотеки транспорта
драйверы
SDK
логирование
нативные клиенты
интеграции вендоров

А развёртывание остаётся выбором проекта:

JVM
или
Native Image

Эта слоистая свобода полезнее глобальной архитектурной чистоты.


Итоговое резюме развенчания мифа

Миф обычно начинается с одного истинного утверждения:

Kora избегает отражения и динамических прокси.

Затем он некорректно расширяет область действия.

Точная версия:

Kora избегает отражения и динамических прокси
для тех механизмов фреймворка, которыми владеет Kora.

Оно не значит:

Никакой сторонний код не может использовать отражение.
Никакой прокси не может существовать в процессе.
Никакие метаданные рантайма не могут читаться.
Каждая зависимость должна быть сгенерирована кодом.
Каждая зависимость должна поддерживать Native Image.

Эти утверждения не следуют.

Приложение на Kora — всё ещё обычное JVM-программное обеспечение.


Заключение: этап компиляции там, где Kora владеет проблемой, обычная Java везде остальном

Архитектура этапа компиляции Kora мощна именно потому, что она целевая.

Фреймворк уже знает граф приложения, поэтому валидирует и генерирует граф до запуска.

Он уже знает декларативные HTTP-контракты, поэтому генерирует адаптеры клиента и сервера.

Он уже знает объявления репозиториев, поэтому генерирует реализации репозиториев.

Он уже знает, где применяется AOP, поэтому генерирует подклассы вместо конструирования прокси рантайма.

Это удаляет значительный объём механизмов фреймворка рантайма и делает приложение легче для понимания, быстрее запускающимся и легче компилируемым в Native Image.

Но ничто из этого не превращает всю JVM-экосистему в среду только для этапа компиляции.

Библиотека, использующая отражение, всё ещё может работать.

Библиотека, создающая динамические прокси, всё ещё может работать.

Библиотека, выполняющая поиск метаданных рантайма, всё ещё может работать.

Библиотека, использующая ServiceLoader, JNI или генерацию классов рантайма, всё ещё может работать на JVM по обычным правилам платформы.

Графу зависимостей Kora нужно только знать, как получить компонент.

Ему не нужно, чтобы внутренности компонента принимали философию реализации Kora.

Это ведёт к самому важному различию во всём обсуждении:

модель рантайма Kora
        ≠
модель рантайма сторонней библиотеки
        ≠
ограничения GraalVM Native Image

На HotSpot обычное динамическое поведение Java остаётся обычным динамическим поведением Java.

Если выбран Native Image, проблема совместимости меняется, потому что GraalVM нужна больше информации об отражении, прокси, ресурсах, JNI и динамической загрузке. Некоторые зависимости уже поставляют эти метаданные. Другие покрыты Репозиторием метаданных достижимости GraalVM. Некоторые требуют кастомных метаданных. Небольшое число может фундаментально зависеть от возможностей рантайма, которые не вписываются в модель закрытого мира.

Это вопросы Native Image.

Их не следует путать с тем, работает ли та же библиотека в приложении на Kora на JVM.

История производительности тоже композиционна. Одна рефлексивная зависимость не превращает сгенерированные DI, маршрутизацию, репозитории, маппинги и AOP Kora в фреймворк отражения рантайма. Зависимость вносит собственные издержки на пути, где выполняется. Фреймворк продолжает вносить собственные, гораздо меньшие механизмы рантайма.

Это реалистичная инженерная модель:

Стоимость приложения
=
стоимость фреймворка Kora
+ бизнес-код
+ база данных
+ сеть
+ сторонние библиотеки
+ сериализация
+ телеметрия
+ всё остальное

Kora минимизирует часть, которую контролирует.

Она не притворяется, что контролирует всё.

Вот почему сильнейшая формулировка не в том, что Kora — «чистая экосистема этапа компиляции». Это почти противоположное:

Стратегия реализации фреймворка — не ограничение всего приложения.

Kora говорит:

«Нам здесь не нужно отражение».

Она не говорит:

«Никакая зависимость в вашем приложении никогда не может использовать отражение».

Именно это различие позволяет Kora сочетать определённость этапа компиляции с обычной Java-экосистемой.

И оно ведёт к финальному принципу:

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

Для команд, нацеленных на GraalVM Native Image, есть одно дополнительное правило:

Если Native Image — требование, совместимость с Native Image нужно оценивать для каждой зависимости — ровно как с любым другим Java-стеком. Преимущество Kora в том, что сам фреймворк начинает с гораздо более чистой базы.

Это более сильный аргумент, чем чистота этапа компиляции, потому что он и технически точен, и операционно полезен.

Kora не требует, чтобы JVM-экосистема стала Kora.

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