Знайомство з Quarkus фреймворком для Java-розробника

Вітаю всіх читачів ресурсу!

Приводом для написання цієї статті стало, в першу чергу, бажання поділитись враженнями від вже не зовсім нового фреймворку під назвою Quarkus, який крок за кроком стає відомим гравцем ринку Java-розробки. Цей фреймворк згадується на DOU переважно в контексті вебінарів, дайджестів та планах доповідей для конференцій, тому метою є спроба розказати про Quarkus тим, хто з ним не встиг познайомитись, показати деякі з його реалізацій та підходів, і, звісно, подискутувати в коментарях.

Розпочати пропоную з опису фреймворку від його розробників з команди Red Hat:

«Quarkus is a full-stack, Kubernetes-native Java framework made for Java virtual machines (JVMs) and native compilation, optimizing Java specifically for containers and enabling it to become an effective platform for serverless, cloud, and Kubernetes environments.»

Іншими словами, тепер, коли serverles архітектура і контейнери з різними оркестраторами перестали бути хайпом і зайняли свою частину ринку, починають з’являтись інструменти, які дозволяють працювати з такими технологіями більш ефективно, надаючи розробнику більше варіантів для вибору.

На сайті фреймворку нам доступний Quarkus bootstrap (аналог spring initializr), який дозволяє швидко зібрати проект на основі необхідного функціоналу. Використовувати для цього можна Maven або Gradle. Невеликий перелік бібліотек та імплементацій, які інтегровані у фреймворк:

— RESTeasy як JAX-RS імплементація
— Agroal для роботи з датасорсами (JDBC), Hibernate/Panache ORM, свої клієнти для MongoDB, Neo4j і т. д.
— Vert.x (core, RxJava2, Mutiny) для асинхронної взаємодії з базами, ресурсами, чергами та ін.
— JUnit5, Mockito для тестування, відмінна інтеграція з RESTassured та Testcontainers
— безліч імплементацій MicroProfile специфікацій для OpenAPI, health моніторів, метрик, fault tolerance інструментів (@CircuitBreaker, @Fallback, @Retry etc) та ін.

І ще дуже багато всього на будь-який смак...

Також з коробки доступна підтримка GraalVM та створення native image без складних конфігурацій.
Для розуміння що це таке і як працює дуже рекомендую прослухати випуск 86 підкасту Scalalaz, де безпосередньо один з розробників GraalVM Олег Шелаєв розказує багато цікавої інформації про цю технологію.

Девелоперам, яких цікавить швидкий процес розробки, буде приємно дізнатись про hot/live reloading, що дозволяє при роботі у dev mode автоматично підтягувати зміни в коді під час наступного запиту на ендпоінт без перезапуску сервіса.

Для всіх, кому цього опису буде замало, на сайті фреймворку є прекрасна шпаргалка з детально розписаним функціоналом і змінами по кожній версії. Також є не менш інформативні гайди від команди розробників Quarkus.

Подивимось на невеликі приклади практичного використання того функціоналу, з яким працює кожен бекенд розробник.

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

Способи конфігурації досить стандартні — application.properties або yaml-файли. Писати кастомні провайдери конфігурацій можна.

Дефолтних конфігураційних профілів три: dev, test, prod. Перші два підтягуються для конкретного режима запуску, останній — якщо режим запуску не вказаний. Створювати свої профілі також можливо. Якщо потрібно змінювати режим запуску в рантаймі, або дізнатись/змінити конфігураційний профіль, то використовуємо сутність ProfileManager.

Приклад використання config property для двох режимів (application.yaml):

quarkus: #prod mode
  datasource:
    jdbc.url: jdbc:postgresql://{$DATABASE_URL}/dbname
    ssl: true

"%dev":  #dev mode
  quarkus:
    datasource:
      jdbc.url: jdbc:postgresql://localhost:5432/dbname
      ssl: false

Якщо проперті для конкретного режиму не вказана, вона підтягується з prod конфігурації.

Для інжекту використовуємо або конфігураційні класи з анотацією @ConfigProperties, або інжектимо безпосередньо в поле. Все звично і зручно:

@ConfigProperty(name = "greeting.message") 
String message;

Відсутність явного модифікатора доступу (package-private) до змінних такого типу зумовлена тим, що Quarkus рекомендує не використовувати приватні змінні для DI, або інжектити у приватну змінну за допомогою конструктору. Такі рекомендації направлені на покращення виконання коду у GraalVM.

REST-ресурси

Варіативність використання ресурсів для rest-взаємодії у Quarkus виходить за межі розповсюдженого підходу. Нас не те щоб підштовхують, але точно натякають всіма способами на те, що поділ бекенду на шари — не обов’язок, а конкретний шаблон для конкретних випадків. І імплементація функціоналу має розглядатись з урахуванням вимог до нього, а не будуватись за одним задерев’янілим патерном. Тому, oкрім звичайної імплементації ресурсу з опціонально вбудованими серіалізаторами, фреймворк надає як можливість працювати з базою через методи сутності (PanacheEntity підхід), так і напряму пов’язувати ресурс з ORM.
Трошки прикладів далі.

Entity-клас, успадковуємось від PanacheEntity або PanacheEntityBase:

@Entity
public class Employee extends PanacheEntity {
	// fields
}

Метод в рест-ресурсі:

@GET
@Path("/employee/{id}")
public Response getEmployee(@PathParam("id") Long id) {
    Employee employee = Employee.findById(id);
    // processing logic
}

Зайвих прошарків для findById імплементувати не треба. Достатньо тільки прописати датасорс у конфігураційному файлі. Підготовлених методів чимало: окрім дефолтних вибірок з сортуванням є і використання HQL, і ланцюги операцій. Приклад отримання 4 сторінки виборки зі 100 елементів з конкретним статусом:

return Employee.find("status", Status.Fired)
    .page(Page.of(4, 100)) 
    .list(Sort.by("reason");

Для транзакційних операцій використовуємо знайому @Transactional анотацію.

Якщо готового функціоналу не вистачає, а дописувати у Entity статичні методи не дозволяє ООП-релігія, то звичайний repository патерн до ваших послуг:

@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {

   public Person findByName(String name) {
       return find("name", name).firstResult();
   }

   public void updateStatus(Long id, Status status) {
   	   int counter = update("status = ?1 where id = ?2", status, id);
   	   // counter handling logic
   }
}

А от наступний приклад фанатичним прихильникам ООП краще взагалі не дивитись. Ресурс-репозиторій:

public interface PeopleResource extends PanacheEntityResource<Person, Long> {
}

@ResourceProperties(path = "people")
public interface PeopleResource extends PanacheEntityResource<Person, Long> {

    @MethodProperties(path = "all")
    Response list();

    @MethodProperties(exposed = false)
    void delete(Long id);
}

Періодично, під час перегляду документації, у мене виникає бажання написати хлопцям з Quarkus-команди «горшочек не вари».
Особливо враховуючи те, що є багато простору для вдосконалення функціоналу там, де це дійсно потрібно.

Наприклад, для декількох датасорсів в Quarkus можна використовувати тільки Agroal (JDBC), а ось для Hibernate ORM цей функціонал тільки запланований. Або, з нещодавно відкритих незручностей, альтернатива використання Criteria у Panache ще на етапі планування. І там, де хочеться написати простенький HQL або щось по типу фільтра, доводиться тільки для отримання білдера розмазувати код на

Session session = Panache.getEntityManager().unwrap(Session.class);
CriteriaBuilder cb = session.getCriteriaBuilder();

І це ми ще навіть не почали писати критерій...

Але, здебільшого, функціонал повний і інтуїтивно простий.
ContainerRequestFilter та ContainerResponseFilter допомагають перехоплювати запити відповідно до та після опрацювання на ресурсі. Можна інжектити HttpRequest або UriInfo напряму в ресурсний клас і витягувати з них необхідну інформацію.
Для валідації Quarkus використовує розповсюджений сьогодні Hibernate Validator.

Тестування

Тестовому фреймворку для виконання необхідна анотація @QuarkusTest з quarkus-junit5 модуля, в дефолтному білді також присутня io.rest-assured залежність і вже написаний тест із запитом на ендпоінт. Http-порт для тестів за замовчуванням 8081.

Над тестовим класом можна вказати назву rest-ресурсу:

@TestHTTPEndpoint(GreetingResource.class) 
public class GreetingResourceTest {

    @Test
    public void testHelloEndpoint() {
        given()
          .when().get()    
          .then()
             .statusCode(200)
             .body(is("hello"));
    }
}

Прописувати шлях при запиті до ендпоінту такого ресурсу не потрібно.

Якщо треба запустити конкретний тестовий ресурс до старту самого тесту, то використовуємо QuarkusTestResourceLifecycleManager. Це популярний кейс при використанні фреймворку Testcontainers, або деяких ресурсів Quarkus’а, доступних з коробки (H2DatabaseTestResource).
Все це потім інжектиться в клас з тестами анотацією @QuarkusTestResource.

Управління бінами

Quarkus використовує часткову імплементацію CDI 2.0 специфікації для менеджменту бінів. Ось тут можна подивитись, що відповідає специфікації, а тут що не відповідає, або поки що не імплементовано.

Межі (скоуп) бінів:

ApplicationScoped - сінглтон в межах аплікейшена, який створюється під час виклику його методу у місці інжекту (lazy)
Singleton - сінглтон в межах аплікейшена, але вже не lazy. Створюється одразу в точці інжекту
RequestScoped - один екземпляр на http-запит
Dependent - «non-shareable» бін, екземпляр якого створюється для кожної точки інжекту. Його життєвий цикл пов’язаний з біном, в який він інжектиться
SessionScoped - бін для роботи в межах HttpSession

Інжектити можна тільки у біни зі скоупом ширше за свій. Наприклад, RequestScoped біни у ApplicationScoped.
Також можливий інжект кастомних змінних або бінів (скоуп буде за замовчуванням Dependent):

@ApplicationScoped
public class Producers {

    @Produces 
    List<Integer> values() {
       List<Integer> values = new ArrayList<>();
       values.add(100);
       return values; 
    }
}

@ApplicationScoped
public class SomeConsumer {

   @Inject
   List<Integer> values;

   // some logic
}

Для управління життєвим циклом бінів використовуємо інтерсептори @PostConstruct, @PreDestroy, @AroundConstruct та @AroundInvoke.
Перші два досить відомі, щоб на них зупинятись. Oстанні два використовуються безпосередньо в інтерсепторах, @AroundConstruct викликається після того, як проінжектились всі залежності пов’язаних з цільовим класом інтерсепторів:

   @AroundConstruct 
   Object doSomething(InvocationContext context) throws Exception {
      // ...custom logic
      Object target = context.proceed(); 
      // ...custom logic
      return target;
   } 

Цільовий клас створюється після того, як відпрацювали всі InvocationContext.proceed() методи його інтерсепторів. Після цього виконується @PostConstruct логіка біна.

@AroundInvoke — відрізняється від AroundConstruct тим, що перехоплює виклики методів цільового класу («бізнес» інтерсептор).

Концепція CDI Events дозволяє створювати івенти і спостерігати за ними. Для цього інжектимо спеціальний event-бін у необхідний клас і використовуємо його у методі для створення івенту:

class MyEvent { 
}

@ApplicationScoped
class SomeService {

   @Inject
   Event<MyEvent> event; 

   void doSomething() {
      // ...
      event.fire(new MyEvent()); 
   }
}

@ApplicationScoped
class SomeHandler {

   void handleMyEvent(@Observes MyEvent event) { 
      // ...do something
   }
}

Для обробки старту та закінчення аплікейшeна можна таким чином використовувати вбудовані StartupEvent (відбувається після ініціалізації контексту) та ShutdownEvent.
А якщо потрібно створити кастомний бін після ініціалізації контексту, то проставляємо над ним анотацію @Startup. Вона створює неявний спостерігач за StartupEvent у біні.

На цьому прикладі буду закінчувати цю частину статті.

В наступній спробую розписати більш детально як Quarkus працює в native режимі, його інструменти для роботи з Kubernetes, можливо щось цікаве про реактивність і все, з чим встигну попрацювати більш ніж поверхнево. Якщо це буде комусь цікаво, звісно :)

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

👍ПодобаєтьсяСподобалось4
До обраногоВ обраному5
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Обожаю quarkus. На нем разработка куда быстрее чем на спринге.

Поделитесь примерами и в чем быстрее?

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

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

Ніт. Брати коли прийде розуміння, що спрінг взяв на себе так багато, що тепер, щоб його насетапити вже потрібний окремий фреймворк — спрінг-бут.

Это вообще не так. Я спокойно беру отдельные библиотеки Spring и использую их в связке с Guice.
Если откроете quarkus.io/guides то особой разницы между spring.io/projects не увидите. Что там что там пытаются покрыть как можно больше областей.

Это вообще не так.

Що саме? Спрінг бут з’явився не через те, що спрінг став складним перевантаженим всяким гівном монстром?

«The primary motivation behind developing Spring Boot is to simplify the process for configuring and deploying the spring applications.»

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

перевантаженим всяким гівном монстром

только уменьшает их значимость. Посколько вы можете брать что надо из спринга и собирать свой проект как хотите. А можете буквально в пару строк кода и спрингбут получить вебприложение которое будет отдавать вам жсон из читая данные из постгреса. И бизнесу пофиг что вам не нравится мейнстримный спрингбут просто по каким то субьективным причинам.

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

Так зараз це може пачка інших платформ типу кваркуса. Плей, верт.ікс, мікронаут, etc.
І на цих платформах це робиться ще простіше.

И бизнесу пофиг что вам не нравится мейнстримный спрингбут просто по каким то субьективным причинам.

Саме так. Джун сходу роздуплить верт.ікс, а от зі спрінгом його доведеться пару днів витратити.

Так зараз це може пачка інших платформ типу кваркуса.

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

Саме так. Джун сходу роздуплить верт.ікс,

вы это сейчас серьезно??? Вы когда джуна выпустите вот в такое vertx.io/...​java/#_using_transactions то я хочу посмотреть что он вам натворит. Я фанат вертекса и на текущий момент лучше чем его реактивный постгрес драйвер в джаве нет, но он (вертх) требует квалификации значительно выше чем спринг.

спринг знают на каждом углу

Так це тому що років 5 назад не було альтернатив. Але сьогодні, на щастя, це вже не так. Тому скоро все зміниться.

Вы когда джуна выпустите вот в такое

Так в спрінгу є схожі кейси. Шаг вліво, шаг вправо. І дні витрачені впусту. А бізнесу треба на вчора.

Але сьогодні, на щастя, це вже не так.

Вы так говорите как будто Спринг вас лично обидел :)

Так в спрінгу є схожі кейси. Шаг вліво, шаг вправо. І дні витрачені впусту.

можете пример, когда вы в спринге потратили день а в Вертх бы за 5 минут справились.

Вы так говорите как будто Спринг вас лично обидел :)

Я вважаю його класичним прикладом over engineering. Коли тула для конкретної задачі перетворилась на швейцарській ніж. Я не фанат таких підходів і пихати все докупи.

можете пример, когда вы в спринге потратили день а в Вертх бы за 5 минут справились.

Ні. Вже нічого не згадаю. Я вже як 6 років попрощався зі спрінгом.

Честно я вас не понимаю. Кваркус такой же швейцарский нож как и Спринг в нем добавлено как ИоС так и доступ к базам так и интеграция с AWS Lambda и тд.
И точно также вам никто не мешает взять для конкретной задачи Guice для ИоС, для баз использовать Vertx Postgres который обернут в Flux/Mono от Project Reactor а для REST запросов взять Spring WebFlux и получить сборную солянку по вашему вкусу. И в этом вас ни спринг ни вертх ни ограничивают.

вот в такое

а можно поинтересоватся а что там «такого» кроме пачки кривого интерфейса из ифчиков и булок?

Все поинты про «callback hell» которые я когда — либо видел, состоят в том что автор просто не дошёл до того места в ФП которое предназначено для превращение коллбек хелла в аккуратный вылизанный код. Так что не принимается.

А бизнесу не пофиг когда вы потом срываете сроки пытаясь через тонну ужасных костылей вклячить то что ваш фрейм делает не так как надо?

Абсолютно пофиг, но Кваркус будет не проще в таких делах. Потому что он примерно такой же комбайн как и спринг.

А как дела с gradle?

И допустим, я хочу написать обработчик своей кастомной аннотации, в спринге есть BeanPostProcessor или же аспекты. Что мне нужно реализовать в Кваркусе?

на счет gradle ошибся, поддержка есть.

Ви правильно зазначили, що BeanPostProcessor та аспекти — це інструменти спрінгової архітектури, побудованої навколо IoC контейнера. Quarkus взагалі не використовує контейнерний підхід, і шукати в ньому альтернативи спрінговим інструментам не потрібно. Для створення анотацій у джави є свої інструменти (непоганий гайд), і Quarkus не вигадає нічого в цьому плані більш ефективного.

*не використовує контейнерний підхід = подібний до імплементовано в Spring, в якому все побудовано навколо Aplication Context.
Хотів також дописати, що концепція подій у CDI 2.0 дає можливість підходити до обробки анотацій зі сторони injection point events, але у Quarkus я такої практики не мав. Ближче до вихідних сподіваюсь буде час спробувати.

Підписатись на коментарі