За пределами «магии Spring»: почему Kora спроектирован вокруг прозрачности, гарантий во время компиляции и меньшей когнитивной нагрузки¶
31 августа 2024
Эта статья — письменное сопровождение доклада на JVM Day 2024 о прозрачности, гарантиях во время компиляции и когнитивной нагрузке. Она резюмирует ключевые идеи доклада и то, как они соотносятся с современным Kora.
Этот доклад подходит к Kora с другого угла, чем типичная презентация фреймворка. Это не в первую очередь доклад о бенчмарках, и он не начинается со списка модулей или цифр производительности. Вместо этого он задаёт более неудобный вопрос: какой объём фреймворк-специфичных знаний бэкенд-инженер должен держать в голове просто для того, чтобы корректно использовать фреймворк?
Презентация была сделана на JVM Day 2024, поэтому она отражает эпоху Kora 1.x, а не сегодняшнюю линейку Kora 2.0. Этот исторический контекст важен. Некоторые API, интеграции, детали тестирования, структура документации и выборы реализации эволюционировали с момента доклада, и Kora 2.0 значительно более зрелый и строгий, чем версия, обсуждаемая здесь. Но центральные идеи напрямую узнаваемы в современном Kora: внедрение зависимостей во время компиляции, генерируемый читаемый исходный код, один рекомендованный путь для распространённых проблем, явная конфигурация, тонкие абстракции, прямой SQL, быстрый запуск тестов и предпочтение обычных механизмов языка Java и Kotlin фреймворк-специфичному поведению времени выполнения.
Аргумент докладчика начинается со Spring, но доклад — не просто атака на Spring. Spring используется как точка отсчёта, потому что это доминирующий JVM-фреймворк для бэкенда и потому что его долгая история делает его отличным примером того, что происходит, когда фреймворк накапливает много поколений функций, слоёв совместимости, стилей программирования, соглашений и скрытых правил. Тезис в том, что часть этой гибкости постепенно становится когнитивными накладными расходами для разработчиков приложений.
Kora был создан как попытка решить более узкую проблему: дать бэкенд-инженерам продуктивный фреймворк, одновременно снижая объём фреймворк-специфичных знаний, которые им нужно запоминать.
Контраст можно резюмировать так:
Фреймворк-специфичная экспертиза растёт
↓
больше соглашений
больше скрытых правил
больше поведения времени выполнения
больше крайних случаев
больше «это просто нужно знать»
↓
внимание разработчика уходит
от бизнес-проблемы
Kora старается толкать в противоположном направлении:
Правила языка Java / Kotlin
+
генерация во время компиляции
+
явный граф приложения
+
инспектируемый исходный код
↓
меньше фреймворк-специфичной памяти
↓
больше внимания доступно
для инженерии приложения
Это и есть настоящий предмет доклада.
Внедрение зависимостей не должно требовать стайл-гайда, чтобы оставаться безопасным¶
Презентация начинается с внедрения зависимостей, потому что DI — одна из самых базовых возможностей фреймворка и поэтому полезный тест сложности фреймворка.
В Spring исторически возможны несколько моделей инъекции. Инъекция через конструктор, инъекция в поля, инъекция через сеттеры, конфигурация на основе XML, инъекция на основе квалификаторов, разрешение по имени бина и другие паттерны существовали в разных поколениях экосистемы. Большинство современных команд на Spring сошлись на инъекции через конструктор как предпочтительном подходе, но сам фреймворк всё ещё должен понимать и поддерживать более широкую модель.
Точка докладчика тонкая: тот факт, что команда выбирает инъекцию через конструктор, не означает, что фреймворк гарантирует инъекцию через конструктор. Ответственность переходит к соглашениям, код-ревью, линтерам, обучению команды и старшим разработчикам, которые должны не допускать менее желательные паттерны в кодовую базу.
Теоретическая гибкость выглядит так:
DI в Spring
├── инъекция через конструктор
├── инъекция в поля
├── инъекция через сеттеры
├── разрешение по имени
├── разрешение по квалификатору
├── конфигурация XML
└── исторические вариации
Но многие производственные команды фактически хотят:
Kora выбирает сделать более узкую модель самой моделью фреймворка. Внедрение зависимостей основано на конструкторах. Нет необходимости команде снова и снова учить разработчиков, какой из нескольких поддерживаемых механизмов считается приемлемым внутри.
Это одна из повторяющихся тем презентации: фреймворк может снижать когнитивную нагрузку не только добавлением возможностей, но и отказом выставлять ненужные выборы.
Докладчик не утверждает, что инъекцию через сеттеры технически невозможно понять. Аргумент организационный. Каждая поддерживаемая фреймворком альтернатива становится чем-то, что команде, возможно, придётся обсуждать, запрещать, ревьюить или объяснять.
Типобезопасная квалификация вместо знаний на уровне строк¶
Тот же аргумент применяется к квалифицированному внедрению зависимостей.
Spring поддерживает квалификаторы, но в экосистеме также есть паттерны, где имена бинов или строки становятся частью разрешения. Команды часто решают это кастомными аннотациями-квалификаторами или константами. Это работоспособно, но создаёт ещё одно фреймворк-специфичное соглашение, которое разработчикам нужно выучить.
Kora использует типизированные теги для различения зависимостей. Тег представлен через обычные типы Java, а не через произвольные имена. Это позволяет IDE, компилятору, инструментам рефакторинга и функциям навигации участвовать естественным образом.
Различие концептуально мало:
против:
Но эффект накапливается со временем. Переименования становятся безопаснее. Навигация становится проще. Рефакторинг требует меньше фреймворк-специфичного инструментария. Разработчики могут использовать обычные возможности языка и IDE вместо запоминания ещё одного пространства имён, управляемого контейнером.
Это один из сильнейших примеров философии дизайна Kora: если обычная языковая возможность может решить проблему, предпочитай языковую возможность фреймворк-специфичному параллельному механизму.
Жизненный цикл — где «простые» функции фреймворка становятся скрытыми знаниями¶
Затем доклад переходит к жизненному циклу компонентов.
На первый взгляд жизненный цикл кажется простым: создать объект, инициализировать его, использовать и в итоге уничтожить. Spring поддерживает знакомые механизмы, такие как @PostConstruct, колбэки
уничтожения,
интерфейсы жизненного цикла и другие точки расширения. Проблема возникает, когда несколько таких механизмов взаимодействуют с наследованием, АОП, прокси, колбэками контейнера и фреймворк-специфичным
порядком.
В этот момент даже опытные разработчики Spring могут не знать точный порядок выполнения методов инициализации, применяется ли данный аспект к колбэку жизненного цикла или какой объект — цель или прокси — участвует в конкретной фазе.
Критика докладчика не в том, что у Spring нет документации по жизненному циклу. В том, что понимание полного поведения может требовать знаний, которые не очевидны ни из исходного кода Java, ни естественно выводятся из языка.
Это порождает то, что доклад снова и снова называет «секретными знаниями»: правила, которые опытные разработчики знают, потому что уже обжигались о них, но которые новый разработчик не может вывести, просто читая класс.
Модель жизненного цикла Kora старается держать структуру ближе к обычному коду. Если должен выполняться метод инициализации родителя, обычное наследование и вызовы super могут выразить это
отношение. Если
существует сгенерированный АОП-подкласс, реализация генерируется как читаемый исходный код и может быть открыта напрямую.
Предполагаемый путь отладки становится:
Вопрос:
«Выполняется ли этот аспект здесь?»
↓
Открыть сгенерированный подкласс
↓
Прочитать реальную реализацию
↓
Узнать ответ
Вместо:
Вопрос:
«Выполняется ли этот аспект здесь?»
↓
вспомнить правила жизненного цикла фреймворка
↓
вспомнить правила прокси
↓
вспомнить поведение, зависящее от версии
↓
искать документацию / статью / issue
Это различие — инспектировать вместо запоминания — пожалуй, самая важная тема всей презентации.
Генерируемый исходный код — не деталь реализации¶
Активное использование Kora аннотационных процессоров и генерации кода часто обсуждается как функция производительности. В этом докладе, однако, более важный аргумент — прозрачность.
Фреймворк генерирует обычный исходный код Java или Kotlin для графов зависимостей, АОП-обёрток, JSON-ридеров и писателей, репозиториев, HTTP-адаптеров и другой инфраструктуры. Этот генерируемый код предназначен для понимания.
Докладчик подчёркивает, что если разработчик не уверен, как что-то работает, он может просто открыть сгенерированный класс.
Это меняет отношение между кодом приложения и поведением фреймворка:
Фреймворк с тяжёлым временем выполнения
исходный код
↓
аннотации
↓
внутренности фреймворка
↓
прокси / рефлексия / метаданные времени выполнения
↓
поведение
против:
Генерируемый слой действует как исполняемое объяснение.
Это полезно для отладки, но также снижает объём знаний о фреймворке, которые разработчики должны удерживать. Фреймворку не нужно объяснять каждое взаимодействие прозой, если точная реализация для текущего приложения лежит в каталоге сгенерированных исходников.
Этот принцип остаётся центральным в современном Kora 2.0. Текущий фреймворк значительно созрел по сравнению с версией 2024 года, описанной в докладе, но читаемый генерируемый исходный код по-прежнему одна из его определяющих характеристик.
DI во время компиляции превращает ошибки контейнера в ошибки компилятора¶
Сам граф зависимостей генерируется во время компиляции. Это означает, что отсутствующие зависимости обнаруживаются во время компиляции, а не при запуске приложения.
Различие звучит мало, пока его не поместить в рабочий процесс разработки:
Валидация графа во время выполнения
написать код
↓
скомпилировать
↓
упаковать
↓
запустить приложение
↓
контейнер строит граф
↓
отсутствующий бин
против:
Валидация графа в Kora
написать код
↓
скомпилировать
↓
граф не может быть построен
↓
ошибка компилятора
Вторая модель сокращает цикл обратной связи и переносит структурные ошибки приложения в ту же фазу, где обнаруживаются обычные ошибки типов.
Докладчик также явно противопоставляет Kora динамическим прокси времени выполнения и генерации байткода. Намерение Kora — генерировать читаемый исходный код во время компиляции, а не создавать непрозрачное поведение позже. Опять же, производительность — часть мотивации, но понимание разработчиком не менее важно.
Модули — это явная композиция, а не невидимое обнаружение¶
Модули Kora представлены как интерфейсы, содержащие фабричные методы, которые вносят компоненты в граф зависимостей. Приложение явно подключает внешние модули через обычные языковые конструкции.
Это даёт разработчику прямой контроль над тем, что попадает в приложение.
Модель композиции примерно такова:
Приложение
├── модуль конфигурации
├── HTTP-модуль
├── JSON-модуль
├── модуль базы данных
├── модуль телеметрии
└── модули приложения
Если возможность нужна, её модуль добавляется. Если не нужна, не добавляется.
Докладчик противопоставляет это широкому авто-обнаружению во время выполнения, где добавление библиотеки в classpath может заставить дополнительное поведение фреймворка появляться автоматически.
Философия Kora в том, что явная композиция — не нежелательный шаблонный код, если она делает итоговую структуру приложения очевидной.
Эта идея стала ещё более развитой после доклада. Kora 2.0 имеет более широкую и зрелую поверхность модулей, чем фреймворк 2024 года, но принцип остаётся тем же: модули подключаются по желанию, а граф приложения остаётся явным.
Конфигурация должна делать безопасный случай выбором по умолчанию¶
Затем доклад рассматривает конфигурацию приложения.
Во многих фреймворках определение того, обязателен ли конфигурационный параметр, может включать модули валидации, аннотации, соглашения связывания и фреймворк-специфичные правила. Разработчики должны помнить, какая аннотация валидации подходит и как она себя ведёт.
Kora использует конфигурационные интерфейсы, чьи обязательные значения естественно представлены через контракты методов. Значения по умолчанию могут выражаться методами интерфейса по умолчанию.
Базовая форма:
interface ClientConfig {
String host();
int port();
default Duration timeout() {
return Duration.ofSeconds(2);
}
}
Концептуально:
обязательный метод
→ обязательная конфигурация
метод по умолчанию
→ значение конфигурации по умолчанию
Более широкая точка докладчика в том, что система типов и синтаксис языка должны сообщать как можно больше до введения фреймворк-специфичных аннотаций.
Это повторяющийся паттерн во всём докладе: используй язык в первую очередь, метаданные фреймворка во вторую.
HTTP: генерировать из контракта, предпочитать OpenAPI как источник¶
Kora поддерживает декларативные HTTP-контроллеры и клиенты, но докладчик идёт дальше и описывает разработку с приоритетом OpenAPI как предпочтительный подход.
Вместо ручного написания контракта контроллера и последующей попытки синхронизировать с ним внешних клиентов команда предпочитает:
Спецификация OpenAPI
↓
сгенерировать серверный контракт
↓
реализовать логику приложения
↓
поделиться спецификацией
↓
сгенерировать клиентов
Это другая философия, чем трактовка OpenAPI как документации, генерируемой после реализации.
Контракт API становится исходным артефактом, а не побочным эффектом.
Этот подход снижает семантический дрейф между сервером и клиентами и даёт другим командам машиночитаемое описание, которое они могут потреблять напрямую.
Современный Kora значительно расширил свой генератор OpenAPI с момента этого доклада на JVM Day 2024, но концептуальное предпочтение остаётся узнаваемым.
JSON без рефлексии во время выполнения¶
Сериализация JSON — ещё один пример, где доклад связывает прозрачность и производительность.
JSON-маппер на основе рефлексии должен динамически инспектировать структуру модели. Kora вместо этого генерирует типизированные JSON-ридеры и писатели во время компиляции.
Для модели:
процессор может генерировать код, который напрямую читает и пишет известные поля без повторного обнаружения структуры во время выполнения.
Доклад утверждает, что даже относительно простая операция может дать значимое улучшение производительности, и что выгода растёт по мере увеличения моделей, потому что сериализация и десериализация — работа, привязанная к CPU.
Важный архитектурный момент — не точный процент, приведённый в презентации. В том, что фреймворк уже знает структуру модели во время компиляции, поэтому может генерировать специализированный код вместо многократной интерпретации метаданных во время выполнения.
Та же логика появляется во всём Kora:
известно во время компиляции?
↓ да
сгенерировать специализированный код
↓
делать меньше обобщённой работы во время выполнения
Доступ к базе данных: держать SQL видимым¶
Раздел о базе данных усиливает то же предпочтение прозрачности.
Kora поддерживает генерируемые репозитории, но намеренно не делает ORM центральной моделью персистентности. Интерфейсы репозиториев содержат явный SQL, и Kora генерирует повторяющийся код выполнения и маппинга.
Докладчик признаёт очевидное возражение: если у модели много полей, ручное написание каждого столбца становится неприятным. Kora решает это SQL-макросами, которые раскрывают поля модели во время компиляции.
Итак, желаемый результат — не:
а:
Модель становится:
Контракт репозитория
↓
явный SQL + макросы времени компиляции
↓
генерируемый репозиторий
↓
драйвер базы данных
Разработчики по-прежнему знают, какие индексы, джойны, столбцы, предикаты и база-специфичные функции использует их код.
Доклад явно представляет это как альтернативу непрозрачности ORM. Позиция докладчика в том, что поведение базы данных слишком важно для производственной производительности, чтобы прятать его за изощрённой объектной моделью, которая может генерировать запросы, которых разработчики не ожидали.
Согласна ли с этой позицией каждая команда — другой вопрос, но она внутренне согласована с более широкой философией Kora.
Макросы времени компиляции сохраняют явный SQL без повторяющихся списков¶
Система макросов особенно важна, потому что показывает, как Kora старается не превращать прозрачность в шаблонный код.
Наивная модель явного SQL могла бы требовать:
для каждого репозитория.
С большими моделями это становится неприятным и чреватым ошибками.
Макросы Kora раскрывают части SQL на основе типов параметров или возвращаемых значений метода во время компиляции. Это держит итоговый SQL явным, убирая повторение.
Докладчик также отмечает, что команды могут определять переиспользуемые абстрактные контракты репозиториев для распространённых CRUD-операций и наследовать от них, при этом макросы по-прежнему разрешаются во время компиляции.
Это важный паттерн:
автоматизируй повторение, не перенося важное поведение в непрозрачный слой времени выполнения.
Телеметрия трактуется как производственный базовый уровень¶
Презентация кратко освещает телеметрию, но само включение показательно.
Kora поддерживает логирование, метрики, трассировку и пробы готовности/живости через выделенные модули. Докладчик упоминает метрики, совместимые с Prometheus, и трассировку на основе OpenTelemetry как часть ожидаемого производственного окружения.
Модель фреймворка намеренно модульна: команды добавляют нужные им модули телеметрии, и инструментарий для основных интеграций участвует в том же графе приложения.
На момент этого доклада 2024 года стек телеметрии Kora был уже значительно зрелее, чем в самых ранних презентациях до 1.0. С тех пор линейка 1.2.x продолжила улучшать конфигурацию телеметрии и теги, а Kora 2.0 дополнительно консолидировал наблюдаемость как часть производственной модели времени выполнения.
Тестирование — где когнитивная нагрузка становится дорогой¶
Большая часть доклада посвящена тестированию, потому что тестирование усиливает сложность фреймворка.
Код приложения может быть написан один раз и читаться изредка. Тесты создаются, изменяются, выполняются, отлаживаются и поддерживаются непрерывно. Если тестовую инфраструктуру фреймворка трудно понять, стоимость умножается на каждого разработчика и каждый прогон CI.
Докладчик критикует модель тестирования Spring не потому, что у Spring нет инструментов тестирования, а потому, что их много. Разработчикам приходится рассуждать о кешировании тестового контекста, обнаружении конфигурации, моках, транзакциях, переиспользовании контекста приложения, аннотациях и взаимодействии между разными тестовыми срезами.
Центральная жалоба в том, что тест может начать требовать существенных знаний о тестовом фреймворке, а не о поведении, которое тестируется.
Философия тестирования Kora намеренно проще.
Доклад описывает тестовый контейнер как производный от того, что тест явно объявляет. Если компонент появляется в тесте, он участвует. Если зависимость объявлена как стаб, она стаб. Тестовая конфигурация — это конфигурация, явно предоставленная для этого теста.
Концептуально:
Докладчик называет это по сути моделью «что видишь, то и тестируешь».
По умолчанию граф приложения пересоздаётся для каждого тестового метода, давая тестам изолированный контейнер. Если переиспользование контекста действительно нужно, конфигурацию жизненного цикла JUnit можно использовать намеренно, а не полагаться на сложные неявные правила кеширования.
Обоснование сильно зависит от быстрого запуска Kora. Если построение графа приложения дёшево, агрессивное кеширование контекста становится менее необходимым.
Это ещё одна область, где современный Kora созрел дальше с момента доклада. Текущая модель тестирования предоставляет модификацию графа и замену компонентов, сохраняя тот же базовый принцип: тесты работают с реальным графом Kora, а не с посторонним скрытым тестовым контейнером.
Быстрые интеграционные тесты меняют компромисс тестирования¶
Докладчик утверждает, что когда фреймворк запускается быстро, интеграционное тестирование становится дешевле.
Традиционная причина агрессивно мокать в том, что поднятие реального контекста приложения дорого. Если запуск дёшев, команды могут чаще использовать Testcontainers и тестирование в стиле black-box или интеграционное, не делая цикл обратной связи невыносимым.
Предполагаемая прогрессия:
быстрый запуск фреймворка
↓
дешёвый реальный граф приложения
↓
более реалистичные интеграционные тесты
↓
меньше фреймворк-специфичной настройки тестов
Это не означает, что каждый тест должен быть интеграционным. Смысл в том, что запуск фреймворка не должен заставлять разработчиков в искусственную тестовую архитектуру.
Настоящий враг доклада — когнитивная нагрузка¶
После обсуждения конкретных функций Kora презентация становится более философской.
Докладчик утверждает, что долгоживущие фреймворки накапливают не только функции, но и историческое поведение. Старые API остаются ради совместимости. Сосуществуют несколько способов решить одну и ту же проблему. Некоторые технически поддерживаются, но больше не рекомендуются. Другие имеют тонкие взаимодействия. Старшие инженеры постепенно выучивают эти паттерны, а затем учат им новых разработчиков.
Проблема не в том, что какое-то отдельное правило невозможно выучить.
Проблема — в общем контексте.
Разработчик, работающий со зрелым фреймворком, может нуждаться в запоминании:
какой стиль инъекции приемлем
какой колбэк жизненного цикла выполняется первым
когда существует прокси
пересекает ли самовызов прокси
как ведёт себя @Transactional
какой механизм конфигурации актуален
какой тестовый контекст кешируется
какая комбинация аннотаций безопасна
какой обходной путь применим к этой версии
Каждый пункт управляем.
Вместе они становятся параллельным корпусом знаний, который конкурирует с бизнес-проблемой за внимание.
Заявленная цель Kora — убрать как можно больше этих знаний.
«Магия» часто — другое имя для скрытых правил¶
Один из самых провокационных разделов рассматривает слово «магия».
Сообщества фреймворков часто используют «магию» позитивно для описания поведения, которое появляется автоматически. Докладчик спрашивает, что это слово реально означает в инженерном контексте.
Если разработчики не могут вывести поведение из исходного кода и должны просто помнить, как работает фреймворк, «магия» может быть дружелюбным именем для скрытых правил.
Аргумент доклада намеренно риторический, но лежащая в основе точка серьёзна:
и поэтому:
Kora старается заменить это кодом, за которым можно проследить.
Если что-то неясно, инспектируй сгенерированный исходный код. Если компонент отсутствует, дай компилятору пожаловаться. Если зависимость обязательна, помести её в конструктор. Если существует отношение жизненного цикла, вырази его через обычный код, где возможно.
Фреймворк должен снижать число вещей, которые приходится принимать на веру.
Количество знаний сообщества автоматически не означает качество документации¶
Затем докладчик оспаривает ещё одно распространённое допущение: огромное сообщество и миллионы ответов автоматически означают, что фреймворк проще в использовании.
У Spring, бесспорно, огромные знания сообщества. Но доклад спрашивает, почему так много базовых вопросов всё ещё требуют поиска внешних статей, Baeldung, Stack Overflow, докладов на конференциях, исходного кода или старых issue на GitHub, если фреймворк якобы прост.
Провокационный вывод в том, что знания сообщества могут быть и силой, и свидетельством сложности.
Большая экосистема означает, что кто-то, вероятно, уже решил проблему раньше.
Но необходимость снова и снова искать тривиальное поведение фреймворка не обязательно желательна.
Докладчик утверждает, что официальная документация должна содержать достаточно информации для обычного использования, тогда как более глубокое поведение должно оставаться инспектируемым из кода.
Желаемый путь разработчика:
а не:
официальная документация
↓
поисковая система
↓
Stack Overflow
↓
блог из другой версии
↓
issue на GitHub
↓
попробовать пять ответов
Этот аргумент сильно предвосхищает философию документации, которую Kora позже развил. Современный Kora теперь имеет существенно более широкую англо- и русскоязычную документацию, руководства, запускаемые примеры и прозрачность генерируемого исходного кода по сравнению с состоянием 2024 года, отражённым в докладе.
Больше статей о фреймворке может означать больше фреймворк-специфичной сложности¶
Презентация намеренно ставит под вопрос допущение, что огромное число докладов о «внутренностях Spring» чисто позитивно.
Если разработчикам нужны целые конференционные сессии, чтобы объяснить удивительное поведение одной аннотации, такой как @Transactional, докладчик спрашивает, действительно ли это следует
рассматривать как свидетельство
простоты.
Это не утверждение, что глубокие технические доклады плохи. Смысл в том, что объём объяснения, который требует фреймворк, сам по себе является стоимостью.
Идеальный фреймворк не нуждается в отдельном корпусе фольклора для обычных операций.
Обычное поведение должно быть понятно из исходного кода приложения, официальной документации и генерируемой реализации.
Плагины IDE — ещё один сигнал скрытой сложности¶
Доклад также указывает на специализированную поддержку IDE.
Фреймворк может стать настолько сложным, что разработчики полагаются на выделенные плагины или коммерческие функции IDE, чтобы понять связку, навигацию или поведение времени выполнения.
Хороший инструментарий ценен, но докладчик спрашивает, не компенсирует ли инструментарий модель, за которой трудно следить стандартными языковыми конструкциями.
Kora старается сделать обычные возможности IDE более полезными, представляя отношения через типы и генерируемый исходный код.
Если идентичность зависимости выражается через типы Java, а не строки, навигация работает. Если реализация генерируется как исходный код, «Перейти к определению» работает. Если граф компилируется, работает диагностика компилятора.
Фреймворк задействует языковой инструментарий, а не строит параллельную семантическую вселенную.
Цель Kora — не быть обсуждаемым везде¶
Одно из самых интересных философских утверждений в докладе — что мейнтейнеры Kora не обязательно хотят миллионы вопросов, статей и обсуждений по устранению неполадок.
С маркетинговой точки зрения большое количество контента сообщества обычно считается бесспорным успехом.
Докладчик предлагает другую цель:
лучший исход — разработчики могут эффективно использовать фреймворк, не нуждаясь в частых разговорах о самом фреймворке.
Другими словами, Kora должен быть скучным.
Разработчик должен решать бизнес-проблемы, а не тратить время на освоение головоломок фреймворка.
Желаемое взаимодействие:
нужна функция
↓
прочитать краткую документацию / пример
↓
написать очевидный код
↓
продолжить бизнес-задачу
Это совсем другая метрика успеха, чем максимизация «доли умов» фреймворка.
Это особенно важно для джуниоров и стажёров¶
Команда работает в организации, которая нанимает и обучает много младших инженеров и стажёров. Поэтому докладчик трактует стоимость введения в проект как реальную платформенную метрику.
Фреймворк, требующий от опытных разработчиков снова и снова учить неписаным правилам, потребляет время и нового инженера, и старшего.
Kora старается использовать компилятор, типы и одну рекомендованную модель программирования, чтобы направлять менее опытных разработчиков к приемлемому коду.
Идеальный фреймворк становится архитектурным ограничением, снижающим число плохих выборов, а не ящиком с инструментами, содержащим много выборов, которые команда должна вручную контролировать.
Это тот же принцип «одна проблема — одно решение», который появляется и в других местах дизайна Kora.
Нет одной «фичи Kora»¶
Ближе к концу доклада докладчик отвечает на распространённый вопрос: в чём killer-фича Kora?
Ответ — единой нет.
Kora представлен как накопление улучшений по многим измерениям:
меньше фреймворк-специфичных знаний
+ проще DI
+ генерируемый исходный код
+ валидация во время компиляции
+ явная конфигурация
+ прямой SQL
+ генерируемые маппинги
+ быстрый запуск
+ эффективное время выполнения
+ более простое тестирование
+ встроенная телеметрия
+ лучшее использование ресурсов
Ни один отдельный пункт не обязательно оправдывает смену технологического стека.
Вместе они меняют опыт разработчика и эксплуатации.
Это важный момент позиционирования. Kora не утверждает, что один DI во время компиляции революционен или что одна генерируемая JSON-сериализация меняет разработку бэкенда. Ценность приходит от согласованности по всему фреймворку.
Kora не претендует на замену Spring везде¶
Докладчик явно отвергает идею, что Kora должен стать универсальной заменой Spring.
Если Spring хорошо работает для приложения и команда не испытывает проблем, обсуждаемых в презентации, нет причин мигрировать лишь потому, что существует Kora.
Цель Kora уже: эффективно решать определённый класс проблем бэкенд-сервисов с прозрачной моделью и низкими накладными расходами фреймворка.
Это также причина, почему команда не просто выбрала Micronaut или Quarkus. По их оценке, эти фреймворки решали некоторые измерения — например, запуск и оптимизацию на время сборки — но не всю комбинацию забот, которые они хотели решить.
Эта комбинация значила больше, чем любой отдельный бенчмарк или функция.
Производственное внедрение уже было значительным¶
Одно из самых конкретных утверждений ближе к концу доклада — о внутреннем внедрении.
На момент презентации докладчик говорит, что в инфраструктуре компании было более 400 сервисов на Kora и что создавалось примерно *два новых сервиса на Kora в день *.
Эти числа следует понимать как исторические цифры, сообщённые во время доклада на JVM Day 2024, а не как текущую статистику парка. Но они важны, потому что опровергают идею, что Kora был лишь экспериментальным фреймворком, разработанным центральной платформенной командой.
По словам докладчика, внедрение было в основном движимо разработчиками. Команды приходили к Kora, потому что были недовольны сложностью или профилем ресурсов своего существующего инструментария или потому что верили, что смогут повысить эффективность инженерии.
Паттерн внедрения, описанный в докладе, можно визуализировать просто:
Сообщённое внутреннее внедрение Kora на момент доклада
Существующие сервисы > 400
Новых сервисов/день ~ 2
400+ сервисов
████████████████████████████████████████
Ежедневный рост
День 1 ++
День 2 ++
День 3 ++
...
Числа не представлены как претензия на глобальную долю рынка. Они показывают, что Kora уже вышел за стадию «внутреннего эксперимента» внутри организации.
Фреймворк уже выбирали, а не просто навязывали¶
Докладчик также тщательно отвечает на предсказуемую критику: возможно, разработчиков заставили использовать внутренний фреймворк.
Его утверждение противоположно. Команды выбирали Kora, потому что видели практические выгоды.
Это утверждение важно для повествования, потому что поддерживает более широкий тезис, что опыт разработчика значит столько же, сколько производительность в бенчмарках. Фреймворк, который экономит CPU, но раздражает разработчиков, не распространился бы добровольно.
Предложение Kora в том, что ограничения во время компиляции и меньшее число моделей программирования на самом деле упрощают разработку, как только команда знакомится с фреймворком.
Примечание об исторической версии: это Kora 1.x, а не Kora 2.0¶
Поскольку доклад датирован JVM Day 2024, он предшествует текущему поколению Kora 2.0. Обсуждаемый фреймворк уже гораздо зрелее, чем пред-1.0 Kora, показанный в более ранних докладах, но это всё ещё не современный Kora 2.0.
Несколько принципов сохранились без изменений:
DI во время компиляции
генерируемый читаемый код
тонкие абстракции
явный SQL
типизированная конфигурация
Java + Kotlin
OpenTelemetry
модули по желанию
быстрый запуск
прозрачные тесты
Другие области значительно эволюционировали.
Kora 2.0 консолидировал модель программирования вокруг обычных синхронных контрактов Java/Kotlin, выполняемых на виртуальных потоках, и убрал реактивные/suspend-контракты фреймворка. Модели HTTP, репозиториев, планирования, тестирования, документации и графа приложения прошли дальнейшую доработку. Текущая документация и примеры гораздо шире, чем было доступно на момент этого доклада.
Полезная историческая прогрессия:
Ранний Kora
↓
доказать архитектуру компиляции/генерируемого исходного кода
Kora 1.x
↓
расширить производственную поверхность
стабилизировать интеграции
расти во внутреннем внедрении
улучшить тесты / OpenAPI / телеметрию / инструментарий
Kora 2.0
↓
снова упростить вокруг зрелой
синхронной модели с приоритетом виртуальных потоков
Презентацию 2024 года поэтому лучше всего понимать как зрелое объяснение того, почему существует архитектура Kora, даже несмотря на то, что фреймворк продолжил меняться.
Более глубокое послание: знания о фреймворке должны быть одноразовыми¶
Сильнейшая идея доклада больше, чем Spring или Kora.
Знания о фреймворке имеют низкую переносимую ценность, когда состоят из случайных правил.
Знание HTTP переносимо.
Знание SQL переносимо.
Знание модели памяти JVM переносимо.
Знание ребалансировки групп Kafka переносимо.
Знание того, как ведут себя транзакции, переносимо.
Знание того, какая именно комбинация аннотаций фреймворка создаёт прокси в одной версии одного фреймворка, гораздо менее переносимо.
Kora старается смещать инженерные знания к первой категории.
Это не означает, что у Kora нет собственных концепций. Конечно есть. Разработчикам всё ещё нужно понимать модули, компоненты графа, генерируемый код, конфигурацию, теги, телеметрию, репозитории и тестовые средства.
Амбиция в том, чтобы эти концепции оставались достаточно малыми и предсказуемыми, чтобы не превратиться в отдельную профессию.
Заключение¶
Этот доклад на JVM Day 2024 меньше о списке функций Kora и больше о стоимости сложности фреймворка.
Spring используется во всей презентации, потому что это мощный пример того, что могут дать двадцать лет эволюции: исключительные возможности, огромные знания сообщества, обширное покрытие интеграций и в то же время большое количество исторического поведения, которое разработчики должны понимать, чтобы уверенно использовать фреймворк.
Критика докладчика не в том, что эта история делает Spring непригодным. Критика в том, что сложность фреймворка потребляет внимание разработчика. Соглашения инъекции через конструктор, порядок жизненного цикла, правила квалификаторов, поведение прокси, семантика транзакций, кеширование контекста, аннотации тестирования, связывание конфигурации и другие детали фреймворка — каждая добавляет немного когнитивной нагрузки. За годы разработки суммарно это может стать существенным.
Ответ Kora — сделать фреймворк концептуально меньше, даже когда поверхность функций широка.
Используй только инъекцию через конструктор.
Используй типизированные теги вместо строковой идентичности.
Используй языковое наследование там, где нужно наследование.
Генерируй АОП-подклассы вместо сокрытия поведения прокси.
Генерируй граф зависимостей и проваливай компиляцию, когда его нельзя удовлетворить.
Генерируй JSON-ридеры и писатели вместо повторного обнаружения структуры модели во время выполнения.
Генерируй реализации репозиториев, держа SQL явным.
Предпочитай OpenAPI как общий контракт.
Делай тестовые графы отражающими ровно то, что объявляет тест.
Держи телеметрию и производственные модули подключаемыми по желанию.
И когда разработчики хотят узнать, что сделал фреймворк, позволь им открыть генерируемый код.
Это создаёт совсем другую цель оптимизации:
Не:
«Сколько фреймворк может сделать автоматически?»
А:
«Сколько фреймворк может сделать,
не заставляя разработчика
запоминать, как он это сделал?»
Это различие — фундамент доклада.
Презентация также показывает, что к 2024 году Kora был уже не просто архитектурным экспериментом. Докладчик сообщил о более чем 400 внутренних сервисах и примерно двух новых сервисах, создаваемых каждый день. Это исторические цифры из доклада, но они указывают, что модель была валидирована далеко за пределами небольших демо.
Самое важное утверждение в презентации поэтому не о производительности. Оно о внимании разработчика.
Бэкенд-инженер должен тратить большую часть ментальной энергии на приложение: семантику HTTP, модели данных, SQL, согласованность, конкурентность, обмен сообщениями, сбои, наблюдаемость и бизнес-правила. Фреймворк должен снижать повторяющуюся работу, не создавая второй корпус знаний, конкурирующий с этими заботами.
Именно это Kora пытался оптимизировать в описанной здесь эпохе 1.x, и это остаётся одной из самых ясных нитей, связывающих этот доклад 2024 года с существенно более зрелым фреймворком Kora 2.0 сегодня.