Облачные технологии открыли путь к множеству новых практических областей, среди которых есть и такие, которые ранее были совершенно невозможны. Среди них выделяется бессерверная парадигма: Бессерверные вычисления – это модель выполнения вычислений в облаке, при которой облачный провайдер предоставляет машинные ресурсы по требованию и берет на себя всю работу с серверами, на что его уполномочивает клиент. При бессерверных вычислениях ресурсы не содержатся в энергозависимой памяти; напротив, вычисления выполняются краткими всплесками, после которых результаты сбрасываются в долговременное хранилище. Когда приложение не используется, никаких ресурсов на него не выделяется. - Википедия Чтобы не замкнуться в ограниченной системе единственного поставщика и не впасть в критическую зависимость от него, можно отказаться от эксклюзивного обслуживания, предлагаемого конкретным провайдером, и воспользоваться Kubernetes. В обоих случаях, особенно в первом, срок жизни пода/контейнера невелик. Следовательно, производительность всей системы в целом сильно зависит от того, сколько времени требуется на ее запуск. Определенно это как раз та область, которой JVM не блещет. Именно для решения этой проблемы Oracle предлагает GraalVM, содержащую опережающий AOT-компилятор, преобразующий байт-код в двоичный код. Я слежу за улучшениями GraalVM на протяжении нескольких версий, как в одиночном исполнении, так и в интеграции с Spring Boot. Фреймворк Spring проектировался более 10 лет назад, когда подобных опасений еще не существовало. С другой стороны, пару лет назад на свет появились конкуренты Spring, в которых были взяты на вооружение как облачные технологии, так и AOT-компиляция: Micronaut и Quarkus. Этот пост будет организован в виде туториала, в котором будет рассмотрено: Создание нового проекта Конфигурирование компонента Конфигурирование контроллера Неблокирующий HTTP-клиент Параметризация Тестирование Интеграция с Docker  Генерация образа для GraalVM i Т.д. Для этой цели на Kotlin будет написано приложение, способное запрашивать Marvel API при помощи неблокирующего кода.  Marvel предлагает REST API, позволяющий запрашивать предлагаемые ими данные. Для этого требуется сгенерировать ключ API и закрытый ключ. Для аутентификации необходимо передать в качестве параметров запроса следующую информацию:   Ключ API как есть Метку времени Хеш конкатенации временной метки, закрытого ключа и ключа API, рассчитанный по алгоритму MD5.  Более подробная информация изложена в документации. Команда Spring первой предложила веб-UI через который можно сконфигурировать такой проект: UI называется Spring Initializr. В нем можно сконфигурировать следующие параметры: Сборочный инструмент: Maven или Gradle Язык: Java, Kotlin или Groovy Версия Spring Boot Некоторые метаданные Зависимости Кроме того, в приложении предоставляется REST API для использования интерфейса командной строки и автоматизации повторяющихся задач. IntelliJ IDEA интегрируется с этим REST API, поэтому можно создать новый проект, не покидая при этом IDE. Наконец, приложение размещено на хостинге, а нужный для этого код предоставляется на GitHub по лицензии Apache v2, поэтому вы можете клонировать и конфигурировать его. Приложение спроектировано с расчетом на расширяемость, а также допускает внесение обновлений. У автора есть отдельный пост, в котором рассмотрены различные способы создания компонентов в Spring. Хотя, Spring и предлагает специальный DSL для компонентов, мы напишем их «традиционным» образом: при помощи аннотаций. Для аутентификации нам потребуется MD5-дайджест сообщения. Воспользовавшись Bean DSL, можно сконфигурировать его таким образом: Spring автоматически обнаружит этот класс на этапе запуска благодаря аннотации @SpringBootApplication и инстанцирует компоненты: Spring – первый фреймворк, в котором был введен контроллер на основе аннотаций, реализованный поверх Servlet API. С тех пор аннотации порой сталкиваются с некоторым неприятием. Именно поэтому в Spring были введены декларативные маршруты. В Kotlin работать с ними еще приятнее при помощи Route DSL: Также нужно зарегистрировать маршрутизатор как компонент: С незапамятных времен Spring предлагал блокирующий HTTP в виде RestTemplate в рамках Web MVC. С выходом версии 5 в Spring был введен WebFlux, реактивная альтернатива Web MVC. WebFlux построен на базе Project Reactor, в основе которого, в свою очередь, лежит концепция реактивных потоков. Вероятно, вы знакомы с основополагающими примитивами Project Reactor: Mono: порождает максимум один элемент Flux: порождает 0..N элементов С переходом WebFlux в Spring стали выводить из употребления RestTemplate, отдавая вместо него предпочтение реактивному WebClient. Вот как сделать вызов внутри существующего маршрута:  Мы также хотим получить некоторые параметры и обеспечить их дальнейшее распространение. Среди всего множества параметров, предлагаемых в Marvel API, я решил предоставить три: limit, offset и orderBy. Функция GET принимает (ServerRequest) → ServerResponse в качестве второго параметра. ServerRequest предлагает queryParam(String), чтобы проверить, существует ли параметр запроса. Возвращает Java Optional. С другой стороны, UriBuilder позволяет задавать параметры запроса при помощи функции queryParam(String, String). Между ними можно создать расширение-мостик: Для каждого из параметров Если он присутствует в запросе Поставить его имя и значение в построитель URI  Теперь можно соответствующим образом выполнить вызов: Далее требуется параметризовать приложение: Marvel API требует, чтобы мы проходили аутентификацию, а мы не хотим жестко вшивать в код наши учетные данные. Также в целях тестирования мы хотим быстро менять URL того сервера, на который направляем запрос. Параметризация требует определиться с двумя вещами: как передавать параметры в приложение, и как использовать в нем эти параметры. Spring Boot предусматривает много различных способов передачи параметров. Параметры можно группировать по профилям и активировать как единое целое. В данном случае я решил установить URL сервера в файле YAML внутри приложения как значение по умолчанию, а секретные данные передавать через командную строку. application.yml Управляем префиксом, указывающим, откуда считывать Интеграция с классом данных Kotlin Spring покладист и допускает разные регистры: шашлычный, змеиный или верблюжий  База кода так велика, что не особенно располагает к тестированию, в особенности модульному. Но можно добавить интеграционный тест, который гарантирует, что отклик от API без маршалинга попадет в класс, а потом с применением маршалинга будет отправлен обратно от приложения. В тестах хочется обойтись без привлечения сторонней инфраструктуры: тест не должен проваливаться в случае, когда откажет какая-то зависимость, которую мы не контролируем. Для интеграционных тестов в классе используется аннотация @SpringBootTest: Запускаем приложение на случайном порту, чтобы избежать отказа, который мог бы быть спровоцирован конфликтом портов  MarvelProperties требует параметра, но при тестировании не используется. Коль скоро параметр существует, мы передаем что угодно. TestContainer – это библиотека Java, позволяющая запускать/останавливать контейнеры Docker. Чтобы воспользоваться ею, нам всего лишь требуется снабдить класс нужной для этого аннотацией. Также мы должны сконфигурировать, какие контейнеры хотим использовать: Интеграция с Testcontainers В Java нам нужен член static. В Kotlin он преобразуется в свойство объекта-компаньона Конфигурируем Testcontainers Используем тот образ контейнера, на который поставлена ссылка  MockServer – это контейнер, который можно использовать в качестве заглушки, так, чтобы он возвращал полезную нагрузку, зависящую от входных данных. А теперь начинается самое интересное: Чтобы начать тест, нам нужен как IP-адрес, так и порт, на который нужно передавать параметры для инициализации MavelProperties Чтобы получить IP и порт, нам нужно запустить контейнер, чей жизненный цикл зависит от теста, т.e., сначала нужно запустить тест. Эту курино-яичную проблему можно решить при помощи источников динамических свойств. Требуется для совместимости с Java  Магия! Тест Spring внедряется во время исполнения Добавляется свойство this…​ …​с этим значением, взятым из свойства mockServer  Теперь переходим к тестовому методу: Kotlin позволяет давать тестовым методам описательные текстовые названия  Создание заглушки Абстракция Spring, позволяющая ссылаться на ресурсы пути класса. sample.json – это тестовый образец. When-часть заглушки Then-часть Перейдем к самому тесту. Spring Test предлагает WebTestClient, неблокирующий клиент для тестирования. Он позволяет параметризовать HTTP-запросы, отправлять их и получать в ответ несколько  текучих утверждений. Spring Test внедряет для вас WebTestClient  Ответные утверждения Но на данном этапе тест оканчивается провалом. Мы сконфигурировали приложение при помощи Beans DSL; а значит, должны были явно вызывать beans на этапе запуска приложения. Значит, и тест нам нужно сконфигурировать соответствующим образом, явно:  Ссылка на класс инициализации  В этом разделе предполагается, что вы уже знакомы с нативной версией GraalVM. 1.      Системно-зависимый бинарник: при таком подходе требуется установить на локальной машине GraalVM с расширением native-image. Получившийся в результате бинарник будет зависеть от данной конкретной системы и не будет кроссплатформенным. Для этой цели в Spring Boot есть специальный выделенный профиль: 2.      Образ Docker: при таком подходе собирается контейнеризованная версия приложения. Для нее требуется локальная сборка образа, с применением, например, Docker. На внутрисистемном уровне она опирается на CNCF Buildpacks (но не требует pack). Для этого Spring Boot предоставляет цель Maven: Spring Boot сам позаботится о нативной конфигурации GraalVM и соответствующего кода, а также предусмотрит большинство его зависимостей. Если после этого вам потребуется дополнительно настроить конфигурацию, пожалуйста, пользуйтесь стандартными конфигурационными файлами, напр., e.g., /META-INF/native-image/<groupId>/<artifactId>/reflect-config.json. В качестве альтернативы Spring предлагает выполнить конфигурацию на основе аннотаций. Давайте это сделаем:  Сохранить код, относящийся к TLS Сохранить классы и разрешить рефлексию во время выполнения  При применении второго подхода получим следующий результат: Если подробнее рассмотреть этот образ, то найдем в нем следующие слои: Родительский образ Системные разрешения Сборки Paketo, сертификаты CA  Наш нативный бинарник Исходно облачный двоичный пусковой файл Псевдонимы пускового файла Сгенерированный образ принимает параметры, поэтому работа идет точно так же, как если бы вы запускали приложение Java через командную строку. Теперь можно отправлять запросы, чтобы поиграть с приложением: Spring обладает долгой историей работы с шаблонным кодом, которую он берет на себя, тем самым позволяя разработчикам сосредоточиться на коде, важном для бизнес-логики. В последние годы Spring успешно интегрировался с языком Kotlin, и такая работа стала строиться фантастически удобно.  Да, с распространением облаков экосистеме Spring пришлось подстраиваться под работу с нативной виртуальной машиной GraalVM. Здесь до сих пор есть что улучшить, но с работой такая экосистема справляется.  Спасибо Себастьену Делезу за то, что отрецензировал оригинал этого поста. Весь исходный код к этому посту выложен на Github в формате для Maven.