Советы по работе с Quarkus

Oct 23, 2021

В этой статье вы узнаете несколько полезных советов и приемов, связанных с фреймворком Quarkus. Мы сосредоточимся на функциях, которые выделяют Quarkus среди других Java фреймворков.

Для тех, кто использует Spring Boot, есть аналогичная статья - Spring Boot Tips, Tricks and Techniques.

Это перевод статьи Quarkus Tips, Tricks and Techniques, автор Piotr Minkowski.

Если вы запускаете свои приложения в Kubernetes, то Quarkus, безусловно, будет хорошим выбором в качестве Java фреймворка. У приложений на Quarkus довольно высокая скорость запуска, а также низкое потребление памяти. Вы можете легко скомпилировать своё приложение с помощью GraalVM в нативный бинарный образ. Фреймворк предоставляет множество полезных функций для разработчиков, например, горячая перезагрузка. Я надеюсь, что вы найдете в этой статье советы и приёмы, которые помогут повысить вашу продуктивность при разработке на Quarkus. Или, может быть, это хотя бы убедит вас взглянуть на этот фреймворк, если у вас еще нет опыта работы с ним.

Quarkus tips & tricks

Содержание

  • Совет 1. Используйте командную строку Quarkus
  • Совет 2. Используйте Dev Services при работе с базами данных
  • Совет 3. Используйте упрощенный ORM - Panache
  • Совет 4. Единая конфигурация
  • Совет 5. Развертывание в Kubernetes с Maven
  • Совет 6. Доступ к консоли Dev UI
  • Совет 7. Непрерывное тестирование
  • Совет 8. Собирайте нативные образы с GraalVM в OpenShift
  • Совет 9. Откат транзакции после каждого теста
  • Совет 10. Воспользуйтесь преимуществами поддержки GraphQL
  • Заключение

Я уже публиковал все эти советы в Твиттере в графической форме, показанной ниже. Вы можете получить к ним доступ, используя поиск по хэштегу #QuarkusTips. Я большой поклонник Quarkus (и, честно говоря, Spring Boot тоже :)). Итак, если у вас есть предложения или ваши собственные любимые функции, просто напишите мне в Twitter (@piotr_minkowski). Я обязательно сделаю ретвит вашего твита.

Transaction rallback

Совет 1. Используйте командную строку Quarkus

Как сконфигурировать новое приложение при использовании какого-нибудь популярного Java фреймворка? Вы можете перейти на сайт онлайн-генератора, который обычно предоставляется этими фреймворками. Вы слышали о Spring Initializr? Quarkus предлагает аналогичный сайт, доступный по адресу https://code.quarkus.io/. Но, возможно, вы не знаете, что существует также и инструмент Quarkus CLI. Он позволяет создавать проекты, управлять расширениями и выполнять различные команды, необходимые для сборки, тестирования и запуска приложения. Например, вы можете создать исходный шаблон для нового приложения с помощью всего одной команды, как показано ниже.

$ quarkus create app --package-name=pl.piomin.samples.quarkus \
  -x resteasy-jackson,hibernate-orm-panache,jdbc-postgresql \
  -o person-service \
  pl.piomin.samples:person-service

После выполнения этой команды вы увидите такой результат:

Creation of initial project with Quarkus CLI

Эта команда создает простое REST приложение, которое использует базу данных PostgreSQL при помощи ORM Quarkus. Кроме того, она задаёт имя приложения, Maven groupId и artifactId. После этого вы можете просто запустить приложение. Для этого перейдите в созданный каталог и выполните следующую команду. В качестве альтернативы вы можете выполнить команду mvn quarkus: dev.

$ quarkus dev

Приложение не запускается, так как не настроено соединение с базой данных. Мы должны это делать? Нет! Давайте перейдем к следующему разделу, чтобы понять, почему.

Совет 2. Используйте Dev Services при работе с базами данных

Вы слышали о Testcontainers? Это Java-библиотека, которая позволяет автоматически запускать контейнеры во время тестов. Вы можете запускать различные базы данных, Selenium или что угодно еще, что может работать в контейнере Docker. Quarkus обеспечивает встроенную интеграцию с Testcontainers при запуске приложений в режиме разработки или тестирования. Эта функция называется Dev Services. Более того, вам не нужно ничего делать, чтобы её включить. Просто НЕ ПРЕДОСТАВЛЯЙТЕ URL-адрес для подключения к базе данных и учетные данные!

Вернемся к нашему сценарию. Мы уже создали приложение с использованием Quarkus CLI. Оно содержит все необходимые библиотеки. Итак, единственное, что нам теперь нужно сделать, это запустить демон Docker. Благодаря этому Quarkus попытается запустить PostgreSQL с Testcontainers в режиме разработки. Что в итоге? Наше приложение работает и связано с PostgreSQL, запущенным в Docker, как показано ниже.

Creation of initial project with Quarkus CLI

Итак, мы можем перейти непосредственно к разработке. С помощью команды quarkus dev мы уже включили режим разработки. Благодаря этому мы можем воспользоваться функцией перезагрузки в реальном времени.

Совет 3. Используйте упрощенный ORM - Panache

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

@Entity
public class Person extends PanacheEntity {
    public String name;
    public int age;
    @Enumerated(EnumType.STRING)
    public Gender gender;
}

Благодаря тому, что обработкой доступа к полям управляет Quarkus, когда вы обращаетесь к person.name, вы фактически вызываете геттер getName(), и аналогично для записи полей и сеттеров. Это обеспечивает правильную инкапсуляцию во время выполнения, поскольку все обращения к полям будут заменены соответствующими вызовами геттеров или сеттеров. PanacheEntity также заботится о реализации первичного ключа.

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

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {

    public List<Person> findByName(String name) {
        return find("name", name).list();
    }

    public List<Person> findByAgeGreaterThan(int age) {
        return find("age > ?1", age).list();
    }
}

Наконец, давайте добавим класс REST ресурса.

@Path("/persons")
public class PersonResource {

    @Inject
    PersonRepository personRepository;

    @POST
    @Transactional
    public Person addPerson(Person person) {
        personRepository.persist(person);
        return person;
    }

    @GET
    public List<Person> getPersons() {
        return personRepository.listAll();
    }

    @GET
    @Path("/name/{name}")
    public List<Person> getPersonsByName(@PathParam("name") String name) {
        return personRepository.findByName(name);
    }

    @GET
    @Path("/age-greater-than/{age}")
    public List<Person> getPersonsByName(@PathParam("age") int age) {
        return personRepository.findByAgeGreaterThan(age);
    }

    @GET
    @Path("/{id}")
    public Person getPersonById(@PathParam("id") Long id) {
        return personRepository.findById(id);
    }
}

Кроме того, давайте создадим файл import.sql в каталоге src/main/resources. Он содержит SQL команды для создания тестовых записей в базе данных и будет выполнен при запуске Hibernate ORM.

insert into person(id, name, age, gender) values(1, 'John Smith', 25, 'MALE');
insert into person(id, name, age, gender) values(2, 'Paul Walker', 65, 'MALE');
insert into person(id, name, age, gender) values(3, 'Lewis Hamilton', 35, 'MALE');
insert into person(id, name, age, gender) values(4, 'Veronica Jones', 20, 'FEMALE');
insert into person(id, name, age, gender) values(5, 'Anne Brown', 60, 'FEMALE');
insert into person(id, name, age, gender) values(6, 'Felicia Scott', 45, 'FEMALE');

Наконец, мы можем вызвать наш REST сервис.

$ curl http://localhost:8080/persons

Совет 4. Единая конфигурация

Предположим, что мы не хотим запускать базу данных в Docker, тогда мы должны сконфигурировать соединение в application.properties файле. По умолчанию Quarkus предоставляет 3 профиля: prod, test, dev. Мы можем определить свойства для нескольких профилей внутри одного application.properties, используя синтаксис %{profile-name}.config.name. В нашем случае пусть у нас будет экземпляр базы данных H2, который будет использоваться в 2 профилях - dev и test, и внешний экземпляр PostgreSQL, используемый в профиле prod.

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=${POSTGRES_USER}
quarkus.datasource.password=${POSTGRES_PASSWORD}
quarkus.datasource.jdbc.url=jdbc:postgresql://person-db:5432/${POSTGRES_DB}

%test.quarkus.datasource.db-kind=h2
%test.quarkus.datasource.username=sa
%test.quarkus.datasource.password=password
%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb

%dev.quarkus.datasource.db-kind=h2
%dev.quarkus.datasource.username=sa
%dev.quarkus.datasource.password=password
%dev.quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb

Перед запуском новой версии приложения мы должны не забыть добавить Maven зависимость для H2 в pom.xml.

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jdbc-h2</artifactId>
</dependency>

Вы также можете определить свой собственный профиль и указать свойства, используя его в качестве префикса. А также вы можете определять отдельные файлы конфигурации для конкретного профиля, такие как application-{profile}.properties.

Совет 5. Развертывание в Kubernetes с Maven

Quarkus это фреймворк, который изначально создавался для работы в Kubernetes. Вы можете легко развернуть приложение на Quarkus в кластере Kubernetes, не создавая файлы YAML вручную. Для более сложных конфигураций, например, сопоставления секретов с переменными среды, вы можете использовать application.properties. Для работы с Kubernetes нам нужно включить модуль quarkus-kubernetes. Также есть отдельная реализация для OpenShift.

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-openshift</artifactId>
</dependency>

После этого Quarkus будет генерировать манифесты развертывания во время сборки при помощи Maven. Мы можем включить автоматическое развертывание в текущий кластер Kubernetes, установив для свойства quarkus.kubernetes.deploy значение true. Для развертывания в OpenShift мы должны изменить цель развертывания по умолчанию с kubernetes на openshift.

quarkus.container-image.build = true
quarkus.kubernetes.deploy = true
quarkus.kubernetes.deployment-target = openshift

Предположим, у нас есть некоторая пользовательская конфигурация, которую нужно установить в манифесте развертывания. Наше приложение будет работать в двух репликах и будет автоматически доступно за пределами кластера. Оно также будет использовать значения из Secret для подключения к базе данных PostgreSQL.

quarkus.openshift.expose = true
quarkus.openshift.replicas = 2
quarkus.openshift.labels.app = person-app
quarkus.openshift.annotations.app-type = demo
quarkus.openshift.env.mapping.postgres_user.from-secret = person-db
quarkus.openshift.env.mapping.postgres_user.with-key = database-user
quarkus.openshift.env.mapping.postgres_password.from-secret = person-db
quarkus.openshift.env.mapping.postgres_password.with-key = database-password
quarkus.openshift.env.mapping.postgres_db.from-secret = person-db
quarkus.openshift.env.mapping.postgres_db.with-key = database-name

Теперь всё что нам нужно, это просто собрать наше приложение при помощи Maven. В качестве альтернативы мы можем удалить свойство quarkus.kubernetes.deploy из application.properties и включить его в команде Maven.

$ maven clean package -D<meta charset="utf-8">quarkus.kubernetes.deploy=true

Совет 6. Доступ к консоли Dev UI

После запуска Quarkus-приложения в режиме разработки (mvn quarkus: dev) вы можете получить доступ к консоли Dev UI по адресу http://localhost:8080/q/dev. Чем больше модулей вы включите, тем больше параметров вы сможете настроить там. Одна из моих любимых функций - возможность развертывать приложения в OpenShift. Вместо того, чтобы запускать команду Maven для создания приложения, мы можем просто запустить его в режиме разработки и развернуть с помощью графического пользовательского интерфейса.

Deploy to OpenShoft with Dev UI

Совет 7. Непрерывное тестирование

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

Continuous testing

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

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

Затем мы добавим несколько простых тестов API. Тестовый класс должен быть помечен аннотацией @QuarkusTest. В остальном реализация типична для библиотеки REST Assured.

@QuarkusTest
public class PersonResourceTests {

    @Test
    void getPersons() {
        List<Person> persons = given().when().get("/persons")
                .then()
                .statusCode(200)
                .extract()
                .body()
                .jsonPath().getList(".", Person.class);
        assertEquals(persons.size(), 6);
    }

    @Test
    void getPersonById() {
        Person person = given()
                .pathParam("id", 1)
                .when().get("/persons/{id}")
                .then()
                .statusCode(200)
                .extract()
                .body().as(Person.class);
        assertNotNull(person);
        assertEquals(1L, person.id);
    }

    @Test
    void newPersonAdd() {
        Person newPerson = new Person();
        newPerson.age = 22;
        newPerson.name = "TestNew";
        newPerson.gender = Gender.FEMALE;
        Person person = given()
                .body(newPerson)
                .contentType(ContentType.JSON)
                .when().post("/persons")
                .then()
                .statusCode(200)
                .extract()
                .body().as(Person.class);
        assertNotNull(person);
        assertNotNull(person.id);
    }
}

Мы также можем запускать эти тесты из консоли Dev UI. Для этого вам следует перейти в консоль Dev UI. Внизу страницы вы найдете модуль тестирования, который отвечает за соответствующую панель. Просто щелкните значок Test result, и вы увидите экран, подобный изображенному ниже.

Dev UI Testing panel

Совет 8. Собирайте нативные образы с GraalVM в OpenShift

Вы можете легко создать и запустить нативный бинарный образ вашего Quarkus-приложения используя возможности GraalVM в OpenShift. Для этого используйте всего одну команду и конструктор ubi-quarkus-native-s2i. OpenShift создает приложение с использованием подхода S2I (source-2-image). Конечно, вам будет нужен работающий кластер OpenShift (например, локальный CRC или Developer Sandbox https://developers.redhat.com/products/codeready-containers/overview), а также oc - клиент OpenShift, установленный локально.

$ oc new-app --name person-native \
  --context-dir basic-with-db/person-app \
  quay.io/quarkus/ubi-quarkus-native-s2i:21.2-java11~https://github.com/piomin/openshift-quickstart.git

Совет 9. Откат транзакции после каждого теста

Если вам нужно откатить изменения в данных после каждого теста, не делайте этого вручную. Вместо этого вам просто нужно пометить свой тестовый класс при помощи аннотации @TestTransaction. Откат выполняется каждый раз после завершения метода тестирования.

@QuarkusTest
@TestTransaction
public class PersonRepositoryTests {

    @Inject
    PersonRepository personRepository;

    @Test
    void addPerson() {
        Person newPerson = new Person();
        newPerson.age = 22;
        newPerson.name = "TestNew";
        newPerson.gender = Gender.FEMALE;
        personRepository.persist(newPerson);
        Assertions.assertNotNull(newPerson.id);
    }
}

Совет 10. Воспользуйтесь преимуществами поддержки GraphQL

Это последний из советов Quarkus в этой статье. Однако это одна из моих любимых функций Quarkus. Поддержка GraphQL не является сильной стороной Spring Boot. Зато Quarkus предоставляет очень мощные и удобные в использовании расширения для GraphQL для клиентской и серверной сторон.

Во-первых, давайте добавим зависимости Quarkus, отвечающие за поддержку GraphQL.

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-graphql</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-graphql-client</artifactId>
    <scope>test</scope>
</dependency>

Теперь мы можем создать код, отвечающий за предоставление GraphQL API. Класс должен быть помечен аннотацией @GraphQLAPI. Quarkus автоматически генерирует схему GraphQL из исходного кода.

@GraphQLApi
public class EmployeeFetcher {

    private EmployeeRepository repository;

    public EmployeeFetcher(EmployeeRepository repository){
        this.repository = repository;
    }

    @Query("employees")
    public List<Employee> findAll() {
        return repository.listAll();
    }

    @Query("employee")
    public Employee findById(@Name("id") Long id) {
        return repository.findById(id);
    }

    @Query("employeesWithFilter")
    public List<Employee> findWithFilter(@Name("filter") EmployeeFilter filter) {
        return repository.findByCriteria(filter);
    }
}

Теперь давайте создадим клиентскую часть для работы с нашим сервисом. Нам нужно пометить этот интерфейс с помощью аннотации @GraphQLClientApi.

@GraphQLClientApi(configKey = "employee-client")
public interface EmployeeClient {

    List<Employee> employees();
    Employee employee(Long id);
}

Наконец, мы можем добавить простой JUnit тест. Нам нужно всего лишь внедрить EmployeeClient и воcпользоваться его методами. Если вас интересует более подробная информация о поддержке GraphQL в Quarkus, то прочтите мою статью An Advanced GraphQL with Quarkus.

@QuarkusTest
public class EmployeeFetcherTests {

    @Inject
    EmployeeClient employeeClient;

    @Test
    void fetchAll() {
        List<Employee> employees = employeeClient.employees();
        Assertions.assertEquals(10, employees.size());
    }

    @Test
    void fetchById() {
        Employee employee = employeeClient.employee(10L);
        Assertions.assertNotNull(employee);
    }
}

Заключение

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