«1. Обзор

Quarkus — это фреймворк, состоящий из ядра и набора расширений. Ядро основано на внедрении контекста и зависимостей (CDI), а расширения обычно предназначены для интеграции сторонней инфраструктуры путем предоставления их основных компонентов в виде компонентов CDI.

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

2. Что такое расширение Quarkus

Расширение Quarkus — это просто модуль, который может работать поверх приложения Quarkus. Само приложение Quarkus представляет собой базовый модуль с набором других расширений.

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

3. Запуск Liquibase в обычном Java-приложении

Давайте попробуем реализовать расширение для интеграции Liquibase, инструмента для управления изменениями базы данных.

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

Точкой входа для платформы Liquibase является Liquibase API. Чтобы использовать это, нам нужен файл журнала изменений, ClassLoader для доступа к этому файлу и соединение с базовой базой данных:

Connection c = DriverManager.getConnection("jdbc:h2:mem:testdb", "user", "password");
ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor();
String changLogFile = "db/liquibase-changelog-master.xml";
Liquibase liquibase = new Liquibase(changLogFile, resourceAccessor, new JdbcConnection(c));

Имея этот экземпляр, мы просто вызываем метод update(), который обновляет базу данных, чтобы она соответствовала файл журнала изменений.

liquibase.update(new Contexts());

Цель состоит в том, чтобы представить Liquibase как расширение Quarkus. То есть предоставление конфигурации базы данных и файла журнала изменений через Quarkus Configuration, а затем создание Liquibase API в виде компонента CDI. Это предоставляет средства для записи вызова миграции для последующего выполнения.

4. Как написать расширение Quarkus

С технической точки зрения расширение Quarkus представляет собой многомодульный проект Maven, состоящий из двух модулей. Первый — это модуль времени выполнения, в котором мы реализуем требования. Второй — это модуль развертывания для обработки конфигурации и генерации кода среды выполнения.

Итак, давайте начнем с создания многомодульного проекта Maven с именем quarkus-liquibase-parent, который содержит два подмодуля, среду выполнения и развертывание:

<modules>
    <module>runtime</module>
    <module>deployment</module>
</modules>

5. Реализация модуля среды выполнения

В модуле среды выполнения мы реализуем:

    класс конфигурации для захвата файла журнала изменений Liquibase производитель CDI для предоставления API Liquibase и рекордер, который действует как прокси для записи вызовов вызовов

5.1. Зависимости и плагины Maven

Модуль среды выполнения будет зависеть от модуля quarkus-core и, в конечном итоге, от модулей среды выполнения необходимых расширений. Здесь нам нужна зависимость quarkus-agroal, поскольку нашему расширению нужен источник данных. Мы также добавим сюда библиотеку Liquibase:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-core</artifactId>
    <version>${quarkus.version}</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-agroal</artifactId>
    <version>${quarkus.version}</version>
</dependency>
<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
    <version>3.8.1</version>
</dependency>

Кроме того, нам может понадобиться добавить quarkus-bootstrap-maven-plugin. Этот подключаемый модуль автоматически генерирует дескриптор расширения Quarkus, вызывая цель дескриптора расширения.

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

В любом случае, мы можем найти дескриптор расширения в META-INF/quarkus-extension.properties:

deployment-artifact=com.baeldung.quarkus.liquibase\:deployment\:1.0-SNAPSHOT

5.2. Предоставление конфигурации

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

@ConfigRoot(name = "liquibase", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
public final class LiquibaseConfig {
    @ConfigItem
    public String changeLog;
}

Мы аннотируем класс с помощью @ConfigRoot, а свойства — с помощью @ConfigItem. Таким образом, поле changeLog, которое представляет собой журнал изменений в верблюжьем регистре, будет предоставлено через ключ quarkus.liquibase.change-log в файле application.properties, расположенном в пути к классам приложения Quarkus:

quarkus.liquibase.change-log=db/liquibase-changelog-master.xml

~~ ~ Мы также можем отметить значение ConfigRoot.phase, которое указывает, когда разрешать ключ журнала изменений. В этом случае BUILD_AND_RUN_TIME_FIXED ключ считывается во время развертывания и доступен приложению во время выполнения.

5.3. Предоставление Liquibase API как CDI Bean

Выше мы видели, как запустить миграцию Liquibase из основного метода.

Теперь мы воспроизведем тот же код, но в виде компонента CDI, и воспользуемся для этой цели производителем CDI:

@Produces
public Liquibase produceLiquibase() throws Exception {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(classLoader);
    DatabaseConnection jdbcConnection = new JdbcConnection(dataSource.getConnection());
    Liquibase liquibase = new Liquibase(liquibaseConfig.changeLog, resourceAccessor, jdbcConnection);
    return liquibase;
}

5.4. Запись байт-кода

«На этом шаге мы напишем класс записи, который действует как прокси для записи байт-кода и настройки логики времени выполнения:

@Recorder
public class LiquibaseRecorder {

    public BeanContainerListener setLiquibaseConfig(LiquibaseConfig liquibaseConfig) {
        return beanContainer -> {
            LiquibaseProducer producer = beanContainer.instance(LiquibaseProducer.class);
            producer.setLiquibaseConfig(liquibaseConfig);
        };
    }

    public void migrate(BeanContainer container) throws LiquibaseException {
        Liquibase liquibase = container.instance(Liquibase.class);
        liquibase.update(new Contexts());
    }

}

Здесь мы должны записать два вызова. setLiquibaseConfig для настройки конфигурации и migrate для выполнения миграции. Далее мы рассмотрим, как эти методы записи вызываются процессорами шагов сборки развертывания, которые мы реализуем в модуле развертывания.

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

6. Реализация модуля развертывания

Центральными компонентами расширения Quarkus являются процессоры шагов сборки. Это методы, аннотированные как @BuildStep, которые генерируют байт-код с помощью средств записи и выполняются во время сборки с помощью цели сборки quarkus-maven-plugin, настроенной в приложении Quarkus.

@BuildSteps упорядочены благодаря BuildItems. Они потребляют элементы сборки, созданные на ранних этапах сборки, и создают элементы сборки для более поздних шагов сборки.

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

6.1. Зависимости Maven

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

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-core-deployment</artifactId>
    <version>${quarkus.version}</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-arc-deployment</artifactId>
    <version>${quarkus.version}</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-agroal-deployment</artifactId>
    <version>${quarkus.version}</version>
</dependency>

<dependency>
    <groupId>com.baeldung.quarkus.liquibase</groupId>
    <artifactId>runtime</artifactId>
    <version>${project.version}</version>
</dependency>

Последняя стабильная версия расширений Quarkus одинакова для модуля среды выполнения.

6.2. Реализация процессоров шагов сборки

Теперь давайте реализуем два процессора шагов сборки для записи байт-кода. Процессор первого шага сборки — это метод build(), который записывает байт-код для выполнения в статическом методе инициализации. Мы настраиваем это через значение STATIC_INIT:

@Record(ExecutionTime.STATIC_INIT)
@BuildStep
void build(BuildProducer<AdditionalBeanBuildItem> additionalBeanProducer,
  BuildProducer<FeatureBuildItem> featureProducer,
  LiquibaseRecorder recorder,
  BuildProducer<BeanContainerListenerBuildItem> containerListenerProducer,
  DataSourceInitializedBuildItem dataSourceInitializedBuildItem) {

    featureProducer.produce(new FeatureBuildItem("liquibase"));

    AdditionalBeanBuildItem beanBuilItem = AdditionalBeanBuildItem.unremovableOf(LiquibaseProducer.class);
    additionalBeanProducer.produce(beanBuilItem);

    containerListenerProducer.produce(
      new BeanContainerListenerBuildItem(recorder.setLiquibaseConfig(liquibaseConfig)));
}

Сначала мы создаем FeatureBuildItem, чтобы отметить тип или имя расширения. Затем мы создаем AdditionalBeanBuildItem, чтобы bean-компонент LiquibaseProducer был доступен для контейнера Quarkus.

Наконец, мы создаем BeanContainerListenerBuildItem, чтобы запустить BeanContainerListener после запуска Quarkus BeanContainer. Здесь, в слушателе, мы передаем конфигурацию bean-компоненту Liquibase.

ProcessMigration(), в свою очередь, запишет вызов для выполнения в основном методе, поскольку он настроен с использованием параметра RUNTIME_INIT для записи.

@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
void processMigration(LiquibaseRecorder recorder, 
  BeanContainerBuildItem beanContainer) throws LiquibaseException {
    recorder.migrate(beanContainer.getValue());
}

Здесь, в этом процессоре, мы только что вызвали метод записи migrate(), который, в свою очередь, записывает метод update() Liquibase для последующего выполнения.

7. Тестирование расширения Liquibase

Чтобы протестировать наше расширение, мы сначала начнем с создания приложения Quarkus с помощью плагина quarkus-maven:

mvn io.quarkus:quarkus-maven-plugin:1.0.0.CR1:create\
-DprojectGroupId=com.baeldung.quarkus.app\
-DprojectArtifactId=quarkus-app

Затем мы добавим наше расширение как зависимость в дополнение к расширению Quarkus JDBC, соответствующему нашей базовой базе данных:

<dependency>
    <groupId>com.baeldung.quarkus.liquibase</groupId>
    <artifactId>runtime</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-h2</artifactId>
    <version>1.0.0.CR1</version>
</dependency>

Затем нам понадобится плагин quarkus-maven-plugin в нашем файле pom:

<plugin>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-maven-plugin</artifactId>
    <version>${quarkus.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>build</goal>
            </goals>
        </execution>
    </executions>
</plugin>

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

Затем мы предоставим конфигурацию источника данных через файл application.properties, расположенный в src/main/resources:

quarkus.datasource.url=jdbc:h2:mem:testdb
quarkus.datasource.driver=org.h2.Driver
quarkus.datasource.username=user
quarkus.datasource.password=password

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

quarkus.liquibase.change-log=db/liquibase-changelog-master.xml

Наконец, мы можем запустить приложение либо в режиме разработки:

mvn compile quarkus:dev

Или в рабочем режиме:

mvn clean package
java -jar target/quarkus-app-1.0-SNAPSHOT-runner.jar

8. Заключение

В этой статье мы реализовали расширение Quarkus. В качестве примера мы продемонстрировали, как запустить Liquibase поверх приложения Quarkus.

Полный исходный код доступен на GitHub.