Новости

[Перевод] Миграция со Spring Boot на Quarkus. Магия или безумие?

Quarkus – это стек Java, приспособленный для работы с OpenJDK HotSpot (или OpenJ9 на zSeries) и GraalVM, собранный из оптимизированных библиотек и стандартов Java. Он хорошо подходит для создания сильно масштабируемых приложений, при этом значительно скромнее использует ресурсы CPU и памяти, нежели другие фреймворки Java. Quarkus может работать с традиционными веб-приложениями, бессерверными приложениями и даже с функциями, предоставляемыми как услуга.

Существует много документированных случаев, в которых организации переносили свои приложения на Quarkus. В этой статье рассмотрим один из таких миграционных путей: со Spring Boot на Quarkus. Есть в этом своя магия и свое безумие. Магия – это когда миграция осуществляется как по мановению руки, и ни одной строки кода при этом менять не приходится. Безумие – в том, чтобы попытаться осознать, как все это делается.

Приложение

Приложение – это простая система для управления задачами из разряда “to-do”. Пользователь может вносить в список дела, а затем вычеркивать те, с которыми справится. Эти позиции хранятся в базе данных PostgreSQL. Весь исходный код приложения находится здесь. Есть и версия, где в качестве сборочного инструмента используется не Maven, а Gradle, она находится в ветке gradle.

Запуск базы данных

Для приложения требуется база данных PostgreSQL, поэтому первым делом воспользуемся Docker или Podman, чтобы локально запустить инстанс:

docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name tododb -e POSTGRES_USER=todo -e POSTGRES_PASSWORD=todo -e POSTGRES_DB=tododb -p 5432:5432 postgres:13

Теперь у вас должен работать инстанс PostgreSQL 11.5 на порте 5432. Нужно создать схему tododb, доступную по пользовательскому todo, с паролем todo.

Запуск приложения

Запустите приложение, введя в командной строке команду./mvnw clean spring-boot:run. Если вы хотите воспользоваться Gradle вместо Maven, то сначала переключитесь на ветку gradle (git checkout gradle) и запустите команду ./gradlew clean bootRun.

Увидите стандартный символ Spring Boot:

INFO 70823 --- [  restartedMain] i.q.todospringquarkus.TodoApplication    : Started TodoApplication in 4.392 seconds (JVM running for 5.116)

Обратите внимание, с какой скоростью оно запускается. Мы об этом еще поговорим.

Убедившись, что приложение работает, перейдите к http://localhost:8080 в вашем любимом браузере. Откроется главный экран приложения, как далее на рисунке 1.

Рисунок 1. Начальный экран приложения

 Немного поэкспериментируйте с приложением. Введите в текстовое поле новую задачу (todo) и нажмите Enter. Эта задача отобразится в списке, как показано далее, на рисунке 2:

Добавление новой задачи
Добавление новой задачи
  1. Щелкните по пустому кругу рядом с задачей, чтобы завершить ее, либо уберите отметку из круга, чтобы отметить задачу как незавершенную.

  2. Щелкните X, чтобы удалить задачу.

  3. По ссылке OpenAPI внизу страницы открывается спецификация OpenAPI 3.0 для этого приложения.

  4. Ссылка Swagger UI открывает встроенный Swagger UI, при помощи которого можно непосредственно выполнять некоторые конечные точки RESTful.

  5. Ссылка Prometheus Metrics ведет к конечной точке с метриками Prometheus, которую Prometheus будет время от времени подвергать скрапингу.

  6. Ссылка Health Check открывает встроенную проверку работоспособности, предоставляемую Spring Boot.

Экспериментируйте далее и посмотрите, как все это работает. Не забудьте вернуться, когда закончите! Когда будете готовы, остановите приложение, нажав на клавиатуре сочетание клавиш CTRL-C .

Разберитесь во внутреннем устройстве

Перед нами полноценное приложение Spring Boot, использующее следующие возможности:

  • Spring MVC для создания уровня REST:

    • Откройте src/main/java/io/quarkus/todospringquarkus/TodoController.java – там вы найдете RESTful-контроллер Spring MVC, предоставляющий различные конечные точки, доступные через пользовательский интерфейс.

  • Spring Data JPA для определения, а также для хранения и извлечения реляционных сущностей:

    • Откройте src/main/java/io/quarkus/todospringquarkus/TodoEntity.java – и найдете там сущность Java Persistence API (JPA), представляющую собой реляционную таблицу для хранения задач.

    • Откройте src/main/java/io/quarkus/todospringquarkus/TodoRepository.java – и найдете репозиторий Spring Data JPA Repository, предоставляющий для TodoEntity все операции создания, чтения, обновления и удаления.

  • Spring Boot Actuators для предоставления эксплуатационных возможностей, в том числе, проверок работоспособности и сбора метрик.

  • SpringDoc OpenAPI 3 для генерирования и предоставления информации RESTful API, а также встроенной конечной точки Swagger UI.

  • Prometheus Micrometer Registry для предоставления метрик Prometheus.

  • Откройте src/main/resources/META-INF/resources, чтобы находить, какие компоненты пользовательского интерфейса были применены  .

Конфигурация

Откройте src/main/resources/application.properties – там находится конфигурация приложения:

spring.jpa.hibernate.ddl-auto=create-drop spring.datasource.url=jdbc:postgresql://localhost:5432/tododb spring.datasource.username=todo spring.datasource.password=todo

springdoc.api-docs.path=/openapi springdoc.swagger-ui.path=/swagger-ui

management.endpoints.web.exposure.include=prometheus,health

Откройте src/main/resources/import.sql, там содержится SQL для предзаполнения таблицы базы данных первичным набором данных:

INSERT INTO todo(id, title, completed) VALUES (0, 'My first todo', 'true');

Согласитесь, не так много кода для полнофункционального приложения, которое может гораздо больше, чем “Hello World!”

Магия

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

Волшебство начинается, готовы? Вернитесь в командную строку и выполните команду./.mvnw clean spring-boot:run. Если хотите использовать Gradle вместо Maven, сначала переключитесь на ветку gradle  (git checkout gradle) и запустите команду ./.gradlew clean bootRun.

После этого вы сразу увидите баннер Quarkus и соответствующее стартовое сообщение, а не то, что демонстрировалось в случае со Spring Boot:

INFO  [io.quarkus] (Quarkus Main Thread) todo-spring-quarkus 0.0.1-SNAPSHOT on JVM (powered by Quarkus 2.0.0.Final) started in 2.748s. Listening on: http://localhost:8080

INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.

INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-postgresql, kubernetes, micrometer, narayana-jta, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-health, smallrye-openapi, spring-data-jpa, spring-di, spring-web, swagger-ui]

Подождите, что же случилось? Теперь это уже приложение Quarkus, а никак не Spring Boot? Это же безумие!

Хотите убедиться? Вернитесь в окно браузера (http://localhost:8080 если вы его уже закрыли) и перезагрузите страницу. Увидите тот же самый пользовательский интерфейс, и он будет полностью функционален. Пощелкайте по разным ссылкам внизу страницы. Все они открываются, как и ранее.

Также обратите внимание, сколько времени потребовалось для запуска. Версия Quarkus запускается практически вполовину быстрее, чем Spring, притом, что база кода одна и та же (4,392 сек. для Spring против 2,748 сек. для Quarkus в приведенных примерах). Вот она – сверхзвуковая субатомная Java!

Ничто не изменилось, так как же это все произошло? Умелый маг не раскрывает своих секретов!

Безумие

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

Выполнение

Присмотритесь ко всем тем командам, которые выполняете в приложении:

С Maven:

Spring Boot: ./mvnw clean spring-boot:run

Quarkus: ./.mvnw clean spring-boot:run

С Gradle:

Spring Boot: ./gradlew clean bootRun

Quarkus: ./.gradlew clean bootRun

Заметили разницу? В версии с Quarkus используется свой исполняемый файл (.mvnw/.gradlew). Если открыть эти файлы и как следует их рассмотреть, то понятно, что происходит кое-что любопытное:

.mvnw

#!/bin/sh
./mvnw clean quarkus:dev -Pquarkus

.gradlew

#!/bin/sh
./gradlew -Pprofile=quarkus $@

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

Конфигурация

Вы думаете, что Quarkus умеет читать и понимать конфигурацию Spring Boot внутри src/main/resources/application.properties? Ну, нет. Не забывайте, это же магия. Маг никогда не открывает всей правды. В вышеприведенном примере не была показана Quarkus-специфичная конфигурация. Она спрятана ниже в том же файле.

Если снова открыть src/main/resources/application.properties и прокрутить до самого низа (примерно до строки 58), увидите кое-какую дополнительную конфигурацию:

quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/tododb quarkus.datasource.username=todo quarkus.datasource.password=todo quarkus.datasource.metrics.enabled=true quarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.sql-load-script=import.sql

quarkus.swagger-ui.always-include=true quarkus.swagger-ui.path=/swagger-ui quarkus.micrometer.export.prometheus.path=/actuator/prometheus quarkus.smallrye-health.root-path=/actuator/health quarkus.smallrye-openapi.path=/openapi

Эта конфигурация похожа на конфигурацию Spring Boot, расположенную вверху файла. Но она, в частности, переопределяет пути для Prometheus и конечные точки, отвечающие за проверку работоспособности, так что эти пути совпадают с путями к конечным точкам актуатора Spring Boot. Именно поэтому ссылки на Prometheus Metrics и Health Check в самом низу экрана продолжают работать, и для этого не требуется вносить никаких изменений в пользовательский интерфейс.

Весьма хитро, верно?

Зависимости

Как вы уже представляете, здесь множество зависимостей, которые требуется изменять, добавлять или обновлять. К счастью, Quarkus предоставляет множество расширений для совместимости со Spring:

  • Расширение Quarkus для внедрения зависимостей в Spring

  • Расширение Quarkus для Spring Web

  • Расширение Quarkus для Spring Data JPA

  • Расширение Quarkus для Spring Boot Properties

Если просто заменить некоторые зависимости Spring Boot на соответствующие зависимости Quarkus, это уже многое даст. Есть и некоторые другие составляющие, в частности, метрики Prometheus, документация OpenAPI, интеграция с Swagger UI и проверки работоспособности, для реализации которых потребуется добавлять и другие зависимости. Опять же, к счастью для нас, в Quarkus предусмотрены и такие возможности:

  • Расширение Quarkus для OpenAPI и Swagger UI

  • Расширение Quarkus для проверки работоспособности MicroProfile

  • Расширение Quarkus для метрик Micrometer

Теперь вы, вероятно, думаете: «Но я же вообще никаких изменений не вносил. Я просто выполнил команду Maven или Gradle, и все приложение запустилось именно под Quarkus, а не под Spring Boot. Как такое возможно?»

Настройка сборки

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

Maven

В ветке mainоткройте файл pom.xml. Сразу же увидите, что файл сборки разбит на множество профилей Maven profiles для spring иquarkus, причем, профиль spring задан по умолчанию. От этих профилей зависит, какие зависимости и плагины сборки будут включаться при запуске либо./mvnw clean spring-boot:run либо ./mvnw -Pquarkus clean quarkus:dev (это маскируется при помощи некоторого перенаправления в вышеупомянутом скрипте .mvnw).

Gradle

С другой стороны, в Gradle нет такой концепции как профиль. Вы заметите в  gradle –ветке проекта несколько файлов .gradle:

  • build-common.gradle

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

  • build-spring.gradle

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

  • build-quarkus.gradle

    • Содержит всю логику сборки, все плагины и зависимости, специфичные для Quarkus-версии приложения. build-quarkus.gradle даже вводит задачу bootRun из Spring Boot , повторно отображая ее на задачу quarkusDev из плагина Quarkus Gradle.

  • settings.gradle

    • Вот тут-то и творится магия! Файл settings.gradle ищет свойство проекта Gradle,  которое называется profile:

      • Если это свойство не найдено, то по умолчанию принимается значение spring.

      • Затем задается, какой файл сборки проекта будет использоваться в качестве основного, build-spring.gradle, или build-quarkus.gradle.

В принципе, это очень дельный подход, если вам нужно сымитировать в Gradle возможности профилей Maven.

Главный класс приложения

В начале этой статьи упоминалось, что мы хотели осуществить миграцию, не изменив ни единой строки кода. В каждом приложении Spring Boot необходим класс “application”, который содержит метод main и аннотирован @SpringBootApplication. В нашем проекте такой класс – это src/main/java/io/quarkus/todospringquarkus/TodoApplication.java.

Quarkus не требует такого класса, а также нет таких расширений для совместимости Quarkus и Spring, которые позволяли бы разрешать аннотацию @SpringBootApplication, нет и ссылки на класс SpringApplication внутри такого главного класса.

Итак, что же получается? Мы не внесли в код вообще никаких изменений, но рассмотренные классы будто бы совершенно нормально разрешаются в Quarkus.

Вы заметите один странный комментарий как в файле pom.xml (для Maven), так и вbuild-quarkus.gradle (для Gradle), прямо над объявлением зависимости для org.springframework.boot:spring-boot-autoconfigure:

This dependency is a hack for TodoApplication.java, which isn’t required for Quarkus. Point of demo is to NOT have any code changes.

(Эта зависимость – хак для TodoApplication.java и не требуется для Quarkus. Суть демо в том, чтобы НЕ вносить никаких изменений в код).

Вот ключевой элемент фокуса. Эта зависимость позволяет как Spring Boot, так и Quarkus разрешать эти классы во время сборки. Эта зависимость объявляется как optional в Maven и compileOnly в Gradle, то есть, никогда не будет включаться в тот двоичный файл, который получается в результате сборки Quarkus. Она будет включаться в результат сборки Spring Boot, так как от нее также зависят другие зависимости spring-boot-starter-*, поэтому она включается транзитивно.

Заключение

В этой статье было показано, как взять имеющееся приложение Spring Boot и запустить его под Quarkus, не внося в код ровно никаких изменений. Это была магия или безумие? Или и того, и другого понемножку? Решайте сами.

Здесь было показано, как можно осуществить миграцию приложения со Spring Boot на Quarkus (причем, это приложение умеет больше, чем вывести “Hello World”) при жестком требовании не менять ни строки исходного кода. Это далеко не единственный возможный путь миграции. Кроме того, возможны случаи, когда приложение использует некую библиотеку или API, для которых нет эквивалента в Quarkus. В таких случаях все-таки может оказаться необходимо изменить код или произвести некоторый рефакторинг.

Автор горячо благодарит Эрика Мерфи. Именно он – автор описанного приложения, и это он придумал такой волшебный фокус.

Ссылки

Весь исходный код к этой статье находится здесь. В ветке main содержится версия Maven, а в ветке gradle версия Gradle. Исходный код приложения в обеих ветках одинаков.

Добавить комментарий

Кнопка «Наверх»