Используем Liquibase с реактивным Quarkus

В этой статье мы разберемся, как настроить запуск Liquibase миграций в реактивном Quarkus сервисе.

· 3 минуты на чтение
Используем Liquibase с реактивным Quarkus

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

Обычно в проектах с базой данных используют систему миграций, например Liquibase. Но использовать Liquibase вместе с реактивным драйвером Postgres попросту не получится, так как Liquibase не поддерживает такие драйверы на данный момент (GitHub Issue). Но есть способ обойти эту проблему.

Возможно вас озарила отличная идея: будем использовать Flyway вместо Liquibase. Спешу вас расстроить, у Flyway тоже есть проблема (GitHub Issue).
Спонсор поста

Используемые версии

Java: 17
Quarkus: 2.8.1
Postgres: 13

Для работы Liquibase в Quarkus c Postgresql нам нужны всего две зависимости:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-liquibase</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>

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

<databaseChangeLog
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

    <changeSet id="create-table-person" author="uPagge">
        <createTable tableName="person">
            <column name="id" type="int" autoIncrement="true">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="first_name" type="varchar(64)"/>
        </createTable>
    </changeSet>

</databaseChangeLog>

Также необходимо в файл application.properties добавить строку, которая запустит миграции при старте сервиса. Без нее Liquibase не будет работать.

quarkus.liquibase.migrate-at-start=true

По умолчанию quarkus ищет миграции в файле resource/db/changeLog.xml. Но вы можете установить свой путь до файла, используя параметр quarkus.liquibase.change-log.

Тперь мы можем запустить наше приложение... И ничего не происходит. Приложение запущено, но никакие скрипты миграции не были выполнены. Хотя, если вы сделаете то же самое с использованием JDBC драйвера, то все будет работать.

Решение проблемы

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

Второе решение мы рассмотрим сейчас. При старте приложения, мы будем руками создавать соединение с базой данных, используя обычный, Postgres JDBC Client. И через это соединение накатывать схему.

Для этого создаем новый класс конфигурации LiquibaseConfig:

package dev.struchkov.example.quarkus.conf; import io.quarkus.runtime.StartupEvent; import liquibase.Contexts; import liquibase.LabelExpression; import liquibase.Liquibase; import liquibase.database.DatabaseConnection; import liquibase.database.DatabaseFactory; import liquibase.exception.LiquibaseException; import liquibase.resource.ClassLoaderResourceAccessor; import liquibase.resource.ResourceAccessor; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.enterprise.event.Observes; import javax.inject.Singleton;
@Singleton public class LiquibaseConfig { private static final Logger log = LoggerFactory.getLogger(LiquibaseConfig.class); @ConfigProperty(name = "quarkus.datasource.jdbc.url") String datasourceUrl; @ConfigProperty(name = "quarkus.datasource.username") String datasourceUsername; @ConfigProperty(name = "quarkus.datasource.password") String datasourcePassword; @ConfigProperty(name = "quarkus.liquibase.change-log", defaultValue = "db/changeLog.xml") String changeLogLocation; @ConfigProperty(name = "quarkus.liquibase.clean-at-start") boolean cleanAtStart; public void runLiquibaseMigration(@Observes StartupEvent event) { final ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(Thread.currentThread().getContextClassLoader()); try (final Liquibase liquibase = new Liquibase( changeLogLocation, resourceAccessor, DatabaseFactory.getInstance() .openConnection(datasourceUrl, datasourceUsername, datasourcePassword, null, resourceAccessor)) ) { if (cleanAtStart) { log.warn("Liquibase drop database"); liquibase.dropAll(); } liquibase.update(new Contexts(), new LabelExpression()); } catch (LiquibaseException e) { log.error("Liquibase error: {}", e.getMessage()); } catch (Exception e) { log.error(e.getMessage(), e); } } }

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

Наш метод  runLiquibaseMigration запустится после запуска приложения, благодаря параметру метода @Observes StartupEvent event.

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

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.3.4</version>
</dependency>
Актуальная версия в Maven Central

И необходимо изменить файл application.properties:

quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_liquibase
quarkus.datasource.jdbc=false
quarkus.datasource.username=postgres
quarkus.datasource.reactive.url=postgresql://localhost:5432/quarkus_liquibase
quarkus.datasource.password=
quarkus.liquibase.migrate-at-start=true

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

Резюмирую

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

Struchkov Mark
Struchkov Mark
Задавайте вопросы, если что-то осталось не понятным👇