Quarkus

Quarkus

Quarkus - Supersonic Subatomic Java. A Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards.
Hans  Marvin

Hans Marvin

1659806940

Top 10 Tips and Tricks Related To The Quarkus Framework

In this article, you will learn some useful tips and tricks related to the Quarkus framework. We will focus on the features that stand Quarkus out from the other frameworks

If you run your applications on Kubernetes, Quarkus is obviously a good choice. It starts fast and does not consume much memory. You may easily compile it natively with GraalVM. It provides a lot of useful developers features like e.g. hot reload. I hope you will find there tips and techniques that help to boost your productivity in Quarkus development. Or maybe just convince you to take a look at it, if don’t have any experience yet.

See more at: https://piotrminkowski.com/2021/10/12/quarkus-tips-tricks-and-techniques/

#quarkus #java #tips

Top 10 Tips and Tricks Related To The Quarkus Framework
Thierry  Perret

Thierry Perret

1659799680

Top 10 Des Trucs Et Astuces Liés Au Framework Quarkus

Dans cet article, vous apprendrez quelques trucs et astuces utiles liés au framework Quarkus. Nous nous concentrerons sur les fonctionnalités qui distinguent Quarkus des autres frameworks

Si vous exécutez vos applications sur Kubernetes, Quarkus est évidemment un bon choix. Il démarre rapidement et ne consomme pas beaucoup de mémoire. Vous pouvez facilement le compiler nativement avec GraalVM. Il fournit de nombreuses fonctionnalités utiles aux développeurs, comme par exemple le rechargement à chaud. J'espère que vous y trouverez des conseils et des techniques qui vous aideront à augmenter votre productivité dans le développement Quarkus. Ou peut-être simplement vous convaincre d'y jeter un coup d'œil, si vous n'avez pas encore d'expérience.

Table des matières

  • Astuce 1. Utilisez l'outil de ligne de commande Quarkus
  • Astuce 2. Utilisez les services de développement avec des bases de données
  • Astuce 3. Utilisez un ORM simplifié avec Panache
  • Astuce 4. Configuration unifiée en option
  • Astuce 5. Déployer sur Kubernetes avec Maven
  • Astuce 6. Accéder à la console Dev UI
  • Astuce 7. Testez en continu
  • Astuce 8. Compiler nativement avec GraalVM sur OpenShift
  • Astuce 9. Rollback transaction après chaque test
  • Astuce 10. Profitez du support de GraphQL
  • Réflexions finales

Astuce 1. Utilisez l'outil de ligne de commande Quarkus

Comment démarrez-vous une nouvelle application lorsque vous utilisez l'un des frameworks Java populaires ? Vous pouvez vous rendre sur le site Web du générateur en ligne, qui est généralement fourni par ces frameworks. Avez-vous entendu parler de Spring Initializr ? Quarkus propose un site similaire disponible à l' adresse https://code.quarkus.io/ . Mais ce que vous ne savez peut-être pas, il y a aussi l'outil Quarkus CLI. Il vous permet de créer des projets, de gérer des extensions et d'exécuter des commandes de construction et de développement. Par exemple, vous pouvez créer un code source pour une nouvelle application à l'aide d'une seule commande, comme indiqué ci-dessous.

$ 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

Après avoir exécuté la commande visible ci-dessus vous devriez avoir un écran similaire.

quarkus-conseils-cli

Cette commande crée une application REST simple qui utilise la base de données PostgreSQL et la couche ORM Quarkus. En outre, il définit le nom de l'application, Maven groupIdet artifactId. Après cela, vous pouvez simplement exécuter l'application. Pour ce faire, accédez au répertoire généré et exécutez la commande suivante. Alternativement, vous pouvez exécuter la mvn quarkus:devcommande.

$ quarkus dev

L'application ne démarre pas correctement car aucune connexion à la base de données n'est configurée. Est-ce qu'on est obligé de faire ça ? Non! Passons à la section suivante pour voir pourquoi.

Astuce 2. Utilisez les services de développement avec des bases de données

Avez-vous entendu parler de Testcontainers ? Il s'agit d'une bibliothèque Java qui vous permet d'exécuter automatiquement des conteneurs lors de tests JUnit. Vous pouvez exécuter des bases de données communes, des navigateurs Web Selenium ou tout autre élément pouvant s'exécuter dans un conteneur Docker. Quarkus fournit une intégration intégrée avec Testcontainers lors de l'exécution d'applications en mode développement ou test. Cette fonctionnalité s'appelle Dev Services. De plus, vous n'avez rien à faire pour l'activer. NE PAS FOURNIR simplement l'URL de connexion et les informations d'identification.

Revenons à notre scénario. Nous avons déjà créé l'application à l'aide de Quarkus CLI. Il contient toutes les bibliothèques nécessaires. Donc, la seule chose que nous devons faire maintenant est d'exécuter un démon Docker. Grâce à cela, Quarkus essaiera d'exécuter PostgreSQL avec Testcontainers en mode développement. Quel est le résultat final ? Notre application fonctionne et est connectée à PostgreSQL démarré avec Docker comme indiqué ci-dessous.

Ensuite, nous pouvons procéder au développement. Avec la quarkus devcommande, nous avons déjà activé le mode de développement. Grâce à cela, nous pouvons profiter de la fonction de rechargement en direct.

Astuce 3. Utilisez un ORM simplifié avec Panache

Ajoutons du code à notre exemple d'application. Nous allons implémenter une couche de données en utilisant Quarkus Panache ORM. C'est un module très intéressant qui vise à rendre vos entités triviales et amusantes à écrire dans Quarkus. Voici notre classe d'entités. Grâce à la réécriture d'accès aux champs de Quarkus, lorsque vous lisez,  person.name vous appelez en fait votre  getName() accesseur, et de même pour les écritures de champs et le setter. Cela permet une encapsulation correcte au moment de l'exécution car tous les appels de champs seront remplacés par les appels getter ou setter correspondants. Le PanacheEntityprend également en charge la mise en œuvre de la clé primaire.

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

Dans l'étape suivante, nous allons définir la classe de référentiel. Puisqu'il implémente l' PanacheRepositoryinterface, nous n'avons qu'à ajouter nos méthodes de recherche personnalisées.

@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();
    }
}

Enfin, ajoutons une classe de ressources avec des points de terminaison 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);
    }

}

Aussi, créons le import.sqlfichier dans le src/main/resourcesrépertoire. Il charge les instructions SQL au démarrage d'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');

Enfin, nous pouvons appeler notre point de terminaison REST.

$ curl http://localhost:8080/persons

 

Astuce 4. Configuration unifiée en option

En supposant que nous ne voulons pas exécuter une base de données sur Docker, nous devons configurer la connexion dans application.properties. Par défaut, Quarkus propose 3 profils : prod, test, dev. Nous pouvons définir des propriétés pour plusieurs profils à l'intérieur d'un seul application.propertiesen utilisant la syntaxe  %{profile-name}.config.name. Dans notre cas, il y a une instance H2 utilisée dans les modes devet testet une instance PostgreSQL externe dans le prodmode.

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

Avant d'exécuter une nouvelle version de l'application, nous devons inclure la dépendance H2 dans Maven pom.xml.

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

Vous pouvez également définir votre profil personnalisé et fournir des propriétés en l'utilisant comme préfixe. Bien sûr, vous pouvez toujours définir des fichiers spécifiques au profil comme application-{profile}.properties.

Astuce 5. Déployer sur Kubernetes avec Maven

Quarkus dans un framework natif Kubernetes. Vous pouvez facilement déployer votre application Quarkus sur le cluster Kubernetes sans créer de fichiers YAML manuellement. Pour des configurations plus avancées comme par exemple le mappage de secrets à des variables d'environnement, vous pouvez utiliser application.properties. D'autres choses comme par exemple les bilans de santé sont détectées dans le code source. Pour activer cela, nous devons inclure le quarkus-kubernetesmodule. Il existe également une implémentation pour OpenShift.

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

Après cela, Quarkus générera des manifestes de déploiement lors de la construction Maven. Nous pouvons activer le déploiement automatique sur le cluster Kubernetes actuel en définissant la propriété quarkus.kubernetes.deploysur true. Pour le déploiement OpenShift, nous devons changer la cible de déploiement par défaut de kubernetesà openshift.

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

Supposons que nous ayons une configuration personnalisée à définir sur le Deploymentmanifeste. Notre application s'exécutera dans deux pods et sera automatiquement exposée en dehors du cluster. Il injecte également des valeurs de Secretafin de se connecter à la base de données 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

Ensuite, nous avons juste besoin de construire notre application avec Maven. Alternativement, nous pouvons supprimer la quarkus.kubernetes.deploypropriété de application.propertieset l'activer sur la commande Maven.

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

 

Astuce 6. Accéder à la console Dev UI

Après avoir exécuté l'application Quarkus en mode dev ( mvn<em> </em>quarkus:dev), vous pouvez accéder à la console Dev UI sous l'adresse http://localhost:8080/q/dev . Plus vous incluez de modules, plus vous pouvez y configurer d'options. L'une de mes fonctionnalités préférées ici est la possibilité de déployer des applications sur OpenShift. Au lieu d'exécuter la commande Maven pour créer une application, nous pouvons simplement l'exécuter en devmode et la déployer à l'aide de l'interface utilisateur graphique.

quarkus-tips-dev-ui

 

Astuce 7. Testez en continu

Quarkus prend en charge les tests continus, où les tests s'exécutent immédiatement après les modifications de code. Cela vous permet d'obtenir un retour instantané sur vos modifications de code. Quarkus détecte quels tests couvrent quel code et utilise ces informations pour n'exécuter les tests pertinents que lorsque le code est modifié. Après avoir exécuté l'application en cours de développement, vous serez invité à activer cette fonctionnalité, comme indiqué ci-dessous. Appuyez simplement sur rpour l'activer.

Ok, alors ajoutons quelques tests pour notre exemple d'application. Tout d'abord, nous devons inclure le module Quarkus Test et la bibliothèque REST Assured.

<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>

Ensuite, nous ajouterons quelques tests d'API simples. La classe de test doit être annotée avec @QuarkusTest. Le reste de l'implémentation est typique de la bibliothèque 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);
    }
}

Nous pouvons également exécuter ces tests JUnit à partir de la console Dev UI. Tout d'abord, vous devez vous rendre sur la console Dev UI. En bas de page, vous trouverez le module de test du responsable du panel. Cliquez simplement sur l' icône Résultat du test et vous verrez un écran similaire à celui visible ci-dessous.

 

Astuce 8. Compiler nativement avec GraalVM sur OpenShift

Vous pouvez facilement créer et exécuter une image Quarkus GraalVM native sur OpenShift à l'aide d'une seule commande et d'un ubi-quarkus-native-s2igénérateur. OpenShift construit l'application en utilisant l'approche S2I (source-2-image). Bien sûr, vous avez juste besoin d'un cluster OpenShift en cours d'exécution (par exemple, CRC local ou Developer Sandbox https://developers.redhat.com/products/codeready-containers/overview ) et le occlient installé localement.

$ 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

Astuce 9. Rollback transaction après chaque test

Si vous devez annuler les modifications apportées aux données après chaque test, évitez de le faire manuellement. Au lieu de cela, il vous suffit d'annoter votre classe de test avec @TestTransaction. La restauration est effectuée chaque fois que la méthode de test est terminée.

@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);
    }
}

 

Astuce 10. Profitez du support de GraphQL

C'est le dernier conseil Quarkus de cet article. Cependant, c'est l'une de mes fonctionnalités Quarkus préférées. La prise en charge de GraphQL n'est pas un côté fort de Spring Boot. D'autre part, Quarkus fournit des extensions très sympas et simples pour GraphQL côté client et côté serveur.

Tout d'abord, ajoutons les modules Quarkus responsables du support de 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>

Ensuite, nous pouvons créer un code responsable de l'exposition de l'API GraphQL. La classe doit être annotée avec @GraphQLAPI. Quarkus génère automatiquement le schéma GraphQL à partir du code source.

@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);
    }

}

Ensuite, créons une interface client pour appeler deux points de terminaison. Nous devons annoter cette interface avec @GraphQLClientApi.

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

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

Enfin, nous pouvons ajouter un simple test JUnit. Nous avons juste besoin d'injecter EmployeeClient, puis d'appeler des méthodes. Si vous souhaitez plus de détails sur la prise en charge de Quarkus GraphQL, lisez mon article 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);
    }
}

Réflexions finales

À mon avis, Quarkus est un framework très intéressant et prometteur. Grâce à ces conseils, vous pouvez facilement démarrer le développement de votre première application avec Quarkus. Il y a quelques nouvelles fonctionnalités intéressantes dans chaque nouvelle version. Alors peut-être que je devrai bientôt mettre à jour cette liste de conseils Quarkus

Lien : https://piotrminkowski.com/2021/10/12/quarkus-tips-tricks-and-techniques/

#quarkus #java #tips

Top 10 Des Trucs Et Astuces Liés Au Framework Quarkus
田辺  亮介

田辺 亮介

1659788760

與 Quarkus 框架相關的 10 大技巧和竅門

在本文中,您將學習一些與 Quarkus 框架相關的有用提示和技巧。我們將專注於讓 Quarkus 從其他框架中脫穎而出的特性

如果你在 Kubernetes 上運行你的應用程序,Quarkus 顯然是一個不錯的選擇。它啟動速度很快,不會消耗太多內存。您可以使用 GraalVM 輕鬆地在本地編譯它。它提供了許多有用的開發人員功能,例如熱重載。我希望您能找到有助於提高 Quarkus 開發效率的技巧和技術。或者也許只是說服你看看它,如果還沒有任何經驗的話。

目錄

  • 提示 1. 使用 Quarkus 命令行工具
  • 提示 2. 將開發服務與數據庫一起使用
  • 提示 3. 使用帶有 Panache 的簡化 ORM
  • 提示 4. 統一配置作為選項
  • 技巧 5. 使用 Maven 部署到 Kubernetes
  • 提示 6. 訪問 Dev UI 控制台
  • 提示 7. 持續測試
  • 技巧 8. 在 OpenShift 上使用 GraalVM 進行本機編譯
  • 技巧 9. 每次測試後回滾事務
  • 提示 10. 利用 GraphQL 支持
  • 最後的想法

提示 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

執行上面可見的命令後,您應該會看到類似的屏幕。

quarkus-提示-cli

此命令創建一個使用 PostgreSQL 數據庫和 Quarkus ORM 層的簡單 REST 應用程序。此外,它設置應用程序的名稱 MavengroupIdartifactId. 之後,您可以運行該應用程序。為此,請轉到生成的目錄並運行以下命令。或者,您可以執行mvn quarkus:dev命令。

$ quarkus dev

應用程序沒有成功啟動,因為沒有配置數據庫連接。我們必須這樣做嗎?不!讓我們繼續下一節看看為什麼。

提示 2. 將開發服務與數據庫一起使用

你聽說過 Testcontainers 嗎?它是一個 Java 庫,允許您在 JUnit 測試期間自動運行容器。您可以運行通用數據庫、Selenium Web 瀏覽器或任何其他可以在 Docker 容器中運行的東西。在開發或測試模式下運行應用程序時,Quarkus 提供與 Testcontainers 的內置集成。此功能稱為開發服務。此外,您無需執行任何操作即可啟用它。只是不要提供連接 URL 和憑據。

讓我們回到我們的場景。我們已經使用 Quarkus CLI 創建了應用程序。它包含所有必需的庫。所以,我們現在唯一需要做的就是運行一個 Docker 守護進程。感謝 Quarkus 將嘗試在開發模式下使用 Testcontainers 運行 PostgreSQL。最後的結果是什麼?我們的應用程序正在運行,它與使用 Docker 啟動的 PostgreSQL 連接,如下所示。

然後,我們可以繼續開發。使用該quarkus dev命令,我們已經啟用了開發模式。多虧了這一點,我們可以利用實時重新加載功能。

提示 3. 使用帶有 Panache 的簡化 ORM

讓我們在示例應用程序中添加一些代碼。我們將使用 Quarkus Panache ORM 實現數據層。這是一個非常有趣的模塊,它專注於讓你的實體在 Quarkus 中編寫變得簡單而有趣。這是我們的實體類。感謝 Quarkus 字段訪問重寫,當您閱讀時, person.name 您實際上會調用您的 getName() 訪問器,對於字段寫入和設置器也是如此。這允許在運行時進行適當的封裝,因為所有字段調用都將被相應的 getter 或 setter 調用替換。PanacheEntity還負責主鍵的實現。

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

在下一步中,我們將定義存儲庫類。由於它實現了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。它在 Hibernate ORM 啟動時加載 SQL 語句。

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 個配置文件:prodtestdevapplication.properties我們可以使用語法  為單個配置文件中的多個配置文件定義屬性%{profile-name}.config.namedev在我們的例子中,在and模式中使用了一個 H2 實例,在模式中使用了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. 使用 Maven 部署到 Kubernetes

Kubernetes 原生框架中的 Quarkus。您可以輕鬆地將 Quarkus 應用程序部署到 Kubernetes 集群,而無需手動創建任何 YAML 文件。對於更高級的配置,例如將機密映射到環境變量,您可以使用application.properties. 在源代碼中檢測到其他內容,例如健康檢查。要啟用此功能,我們需要包含該quarkus-kubernetes模塊。OpenShift 也有一個實現。

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

之後,Quarkus 將在 Maven 構建期間生成部署清單。quarkus.kubernetes.deploy我們可以通過將屬性設置為來啟用對當前 Kubernetes 集群的自動部署true。對於 OpenShift 部署,我們必須將默認部署目標從 更改kubernetesopenshift.

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

假設我們在Deployment清單上設置了一些自定義配置。我們的應用程序將在兩個 pod 中運行,並自動暴露在集群之外。它還注入值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從 Maven 命令中刪除該屬性application.properties並啟用它。

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

 

提示 6. 訪問 Dev UI 控制台

在開發模式 ( ) 下運行 Quarkus 應用程序後,您可以在地址http://localhost:8080/q/devmvn<em> </em>quarkus:dev下訪問 Dev UI 控制台。您包含的模塊越多,您可以在那裡配置的選項就越多。我最喜歡的功能之一是能夠將應用程序部署到 OpenShift。我們可以在模式下運行它並使用圖形 UI 進行部署,而不是運行 Maven 命令來構建應用程序。dev

quarkus-tips-dev-ui

 

提示 7. 持續測試

Quarkus 支持持續測試,在代碼更改後立即運行測試。這使您可以立即獲得有關代碼更改的反饋。Quarkus 檢測哪些測試覆蓋了哪些代碼,並使用此信息僅在代碼更改時運行相關測試。在開發中運行應用程序後,系統將提示您啟用該功能,如下所示。只需按下r即可啟用它。

好的,讓我們為我們的示例應用程序添加一些測試。首先,我們需要包含 Quarkus 測試模塊和 REST Assured 庫。

<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 控制台運行這些 JUnit 測試。首先,您應該轉到 Dev UI 控制台。在頁面底部,您會找到面板負責測試模塊。只需單擊測試結果圖標,您將看到類似於下圖的屏幕。

 

技巧 8. 在 OpenShift 上使用 GraalVM 進行本機編譯

您可以使用單個命令和ubi-quarkus-native-s2i構建器在 OpenShift 上輕鬆構建和運行本機 Quarkus GraalVM 映像。OpenShift 使用 S2I(source-2-image)方法構建應用程序。當然,您只需要一個正在運行的 OpenShift 集群(例如本地 CRC 或 Developer Sandbox https://developers.redhat.com/products/codeready-containers/overview)和oc本地安裝的客戶端。

$ 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 提供了非常酷且簡單的擴展。

首先,讓我們添加負責 GraphQL 支持的 Quarkus 模塊。

<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,然後調用方法。如果您對有關 Quarkus GraphQL 支持的更多詳細信息感興趣,請閱讀我的文章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 開發您的第一個應用程序。每個新版本中都有一些有趣的新功能。所以也許,我將不得不盡快更新這個 Quarkus 提示列表

鏈接:https ://piotrminkowski.com/2021/10/12/quarkus-tips-tricks-and-techniques/

#quarkus #java #tips

與 Quarkus 框架相關的 10 大技巧和竅門
Dang  Tu

Dang Tu

1659777840

10 Mẹo Và Thủ Thuật Hàng đầu Liên Quan đến Khuôn Khổ Quarkus

Trong bài viết này, bạn sẽ tìm hiểu một số mẹo và thủ thuật hữu ích liên quan đến khuôn khổ Quarkus. Chúng tôi sẽ tập trung vào các tính năng nổi bật của Quarkus so với các khuôn khổ khác

Nếu bạn chạy các ứng dụng của mình trên Kubernetes, Quarkus rõ ràng là một lựa chọn tốt. Nó khởi động nhanh và không tốn nhiều bộ nhớ. Bạn có thể dễ dàng biên dịch nó nguyên bản với GraalVM. Nó cung cấp rất nhiều tính năng hữu ích cho nhà phát triển như tải lại nóng. Tôi hy vọng bạn sẽ tìm thấy các mẹo và kỹ thuật giúp tăng năng suất của bạn trong quá trình phát triển Quarkus. Hoặc có thể chỉ thuyết phục bạn xem qua nó, nếu bạn chưa có bất kỳ kinh nghiệm nào.

Mục lục

  • Mẹo 1. Sử dụng công cụ dòng lệnh Quarkus
  • Mẹo 2. Sử dụng Dịch vụ Dev với cơ sở dữ liệu
  • Mẹo 3. Sử dụng ORM đơn giản với Panache
  • Mẹo 4. Cấu hình hợp nhất làm tùy chọn
  • Mẹo 5. Triển khai Kubernetes với Maven
  • Mẹo 6. Truy cập bảng điều khiển giao diện người dùng Dev
  • Mẹo 7. Kiểm tra liên tục
  • Mẹo 8. Biên dịch nguyên bản với GraalVM trên OpenShift
  • Mẹo 9. Giao dịch khôi phục sau mỗi lần thử nghiệm
  • Mẹo 10. Tận dụng hỗ trợ GraphQL
  • Những Thougths cuối cùng

Mẹo 1. Sử dụng công cụ dòng lệnh Quarkus

Làm thế nào để bạn bắt đầu một ứng dụng mới khi sử dụng một trong những khuôn khổ Java phổ biến? Bạn có thể truy cập trang web của trình tạo trực tuyến, thường được cung cấp bởi các khuôn khổ đó. Bạn có nghe nói về Spring Initializr? Quarkus cung cấp một trang web tương tự tại https://code.quarkus.io/ . Nhưng có thể bạn chưa biết, còn có công cụ Quarkus CLI. Nó cho phép bạn tạo các dự án, quản lý các phần mở rộng và thực hiện các lệnh xây dựng và nhà phát triển. Ví dụ: bạn có thể tạo mã nguồn cho một ứng dụng mới bằng một lệnh duy nhất như hình dưới đây.

$ 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

Sau khi thực hiện lệnh hiển thị ở trên, bạn sẽ thấy một màn hình tương tự.

quarkus-tips-cli

Lệnh này tạo một ứng dụng REST đơn giản sử dụng cơ sở dữ liệu PostgreSQL và lớp Quarkus ORM. Ngoài ra, nó đặt tên của ứng dụng, Maven groupIdartifactId. Sau đó, bạn chỉ có thể chạy ứng dụng. Để làm điều đó, hãy chuyển đến thư mục đã tạo và chạy lệnh sau. Ngoài ra, bạn có thể thực hiện mvn quarkus:devlệnh.

$ quarkus dev

Ứng dụng không khởi động thành công, vì không có kết nối cơ sở dữ liệu nào được định cấu hình. Chúng ta có phải làm như vậy không? Không! Chúng ta hãy chuyển sang phần tiếp theo để xem tại sao.

Mẹo 2. Sử dụng Dịch vụ Dev với cơ sở dữ liệu

Bạn có nghe nói về Testcontainers không? Nó là một thư viện Java cho phép bạn chạy các vùng chứa tự động trong các bài kiểm tra JUnit. Bạn có thể chạy cơ sở dữ liệu chung, trình duyệt web Selenium hoặc bất kỳ thứ gì khác có thể chạy trong vùng chứa Docker. Quarkus cung cấp khả năng tích hợp sẵn với Testcontainers khi chạy các ứng dụng ở chế độ dev hoặc test. Tính năng này được gọi là Dịch vụ nhà phát triển. Hơn nữa, bạn không phải làm bất cứ điều gì để kích hoạt nó. Chỉ KHÔNG CUNG CẤP URL kết nối và thông tin đăng nhập.

Hãy quay lại kịch bản của chúng ta. Chúng tôi đã tạo ứng dụng bằng Quarkus CLI. Nó chứa tất cả các thư viện cần thiết. Vì vậy, điều duy nhất chúng ta cần làm bây giờ là chạy một trình nền Docker. Nhờ đó Quarkus sẽ cố gắng chạy PostgreSQL với Testcontainers ở chế độ phát triển. Kết quả cuối cùng là gì? Ứng dụng của chúng tôi đang hoạt động và nó được kết nối với PostgreSQL bắt đầu bằng Docker như hình dưới đây.

Sau đó, chúng tôi có thể tiến hành phát triển. Với quarkus devlệnh, chúng tôi đã kích hoạt chế độ nhà phát triển. Nhờ đó, chúng tôi có thể tận dụng tính năng tải lại trực tiếp.

Mẹo 3. Sử dụng ORM đơn giản với Panache

Hãy thêm một số mã vào ứng dụng mẫu của chúng tôi. Chúng tôi sẽ triển khai một lớp dữ liệu bằng cách sử dụng Quarkus Panache ORM. Đó là một mô-đun rất thú vị tập trung vào việc làm cho các thực thể của bạn trở nên tầm thường và thú vị khi viết trong Quarkus. Đây là lớp thực thể của chúng tôi. Nhờ ghi lại quyền truy cập trường Quarkus, khi bạn đọc,  person.name bạn sẽ thực sự gọi  trình truy cập của mình getName() , và tương tự đối với ghi trường và trình thiết lập. Điều này cho phép đóng gói thích hợp trong thời gian chạy vì tất cả các lệnh gọi trường sẽ được thay thế bằng các lệnh gọi getter hoặc setter tương ứng. Nó PanacheEntitycũng chăm sóc việc triển khai khóa chính.

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

Trong bước tiếp theo, chúng ta sẽ xác định lớp kho lưu trữ. Vì nó triển khai PanacheRepositorygiao diện, chúng tôi chỉ cần thêm các phương pháp tìm tùy chỉnh của mình.

@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();
    }
}

Cuối cùng, hãy thêm một lớp tài nguyên với các điểm cuối 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);
    }

}

Ngoài ra, hãy tạo import.sqltệp trong thư mục src/main/resources. Nó tải các câu lệnh SQL khi ORM Hibernate bắt đầu.

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');

Cuối cùng, chúng ta có thể gọi điểm cuối REST của mình.

$ curl http://localhost:8080/persons

 

Mẹo 4. Cấu hình hợp nhất làm tùy chọn

Giả sử chúng ta không muốn chạy cơ sở dữ liệu trên Docker, chúng ta nên định cấu hình kết nối trong application.properties. Theo mặc định, Quarkus cung cấp 3 cấu hình : prod,, . Chúng ta có thể xác định các thuộc tính cho nhiều cấu hình bên trong một cấu hình duy nhất bằng cách sử dụng cú pháp  . Trong trường hợp của chúng tôi, có một cá thể H2 được sử dụng trong và các chế độ, và một cá thể PostgreSQL bên ngoài trong chế độ.testdevapplication.properties%{profile-name}.config.namedevtestprod

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

Trước khi chạy một ứng dụng phiên bản mới, chúng ta phải đưa sự phụ thuộc H2 vào Maven pom.xml.

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

Bạn cũng có thể xác định cấu hình tùy chỉnh của mình và cung cấp các thuộc tính bằng cách sử dụng nó làm tiền tố. Tất nhiên, bạn vẫn có thể xác định các tệp hồ sơ cụ thể như application-{profile}.properties.

Mẹo 5. Triển khai Kubernetes với Maven

Quarkus trong khung bản địa Kubernetes. Bạn có thể dễ dàng triển khai ứng dụng Quarkus của mình vào cụm Kubernetes mà không cần tạo bất kỳ tệp YAML nào theo cách thủ công. Đối với các cấu hình nâng cao hơn, chẳng hạn như ánh xạ bí mật với các biến môi trường, bạn có thể sử dụng application.properties. Những thứ khác như kiểm tra sức khỏe được phát hiện trong mã nguồn. Để kích hoạt điều này, chúng ta cần bao gồm quarkus-kubernetesmô-đun. Ngoài ra còn có một triển khai cho OpenShift.

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

Sau đó, Quarkus sẽ tạo các bản kê khai triển khai trong quá trình xây dựng Maven. Chúng tôi có thể kích hoạt triển khai tự động cho cụm Kubernetes hiện tại bằng cách đặt thuộc tính quarkus.kubernetes.deploythành true. Để triển khai OpenShift, chúng ta phải thay đổi mục tiêu triển khai mặc định từ kubernetesthành openshift.

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

Giả sử chúng ta có một số cấu hình tùy chỉnh để đặt trên Deploymenttệp kê khai. Ứng dụng của chúng tôi sẽ chạy trong hai nhóm và tự động được hiển thị bên ngoài cụm. Nó cũng đưa các giá trị từ Secretđể kết nối với cơ sở dữ liệu 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

Sau đó, chúng tôi chỉ cần xây dựng ứng dụng của mình với Maven. Ngoài ra, chúng tôi có thể xóa thuộc quarkus.kubernetes.deploytính khỏi application.propertiesvà bật thuộc tính đó trên lệnh Maven.

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

 

Mẹo 6. Truy cập bảng điều khiển giao diện người dùng Dev

Sau khi chạy ứng dụng Quarkus ở chế độ dev ( mvn<em> </em>quarkus:dev), bạn có thể truy cập bảng điều khiển Dev UI theo địa chỉ http: // localhost: 8080 / q / dev . Bạn càng bao gồm nhiều mô-đun, bạn có thể cấu hình nhiều tùy chọn hơn ở đó. Một trong những tính năng yêu thích của tôi ở đây là khả năng triển khai ứng dụng cho OpenShift. Thay vì chạy lệnh Maven để xây dựng ứng dụng, chúng ta có thể chạy nó ở devchế độ và triển khai bằng giao diện người dùng đồ họa.

quarkus-tips-dev-ui

 

Mẹo 7. Kiểm tra liên tục

Quarkus hỗ trợ kiểm tra liên tục, nơi các bài kiểm tra chạy ngay lập tức sau khi thay đổi mã. Điều này cho phép bạn nhận được phản hồi tức thì về các thay đổi mã của bạn. Quarkus phát hiện kiểm tra nào bao gồm mã nào và sử dụng thông tin này để chỉ chạy các kiểm tra liên quan khi mã được thay đổi. Sau khi chạy ứng dụng đang được phát triển, bạn sẽ được nhắc kích hoạt tính năng đó như hình dưới đây. Chỉ cần nhấn rđể kích hoạt nó.

Được rồi, chúng ta hãy thêm một số thử nghiệm cho ứng dụng mẫu của chúng ta. Đầu tiên, chúng ta cần bao gồm mô-đun Kiểm tra Quarkus và thư viện REST Assured.

<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>

Sau đó, chúng tôi sẽ thêm một số thử nghiệm API đơn giản. Lớp thử nghiệm phải được chú thích với @QuarkusTest. Phần còn lại của việc triển khai là điển hình cho thư viện 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);
    }
}

Chúng tôi cũng có thể chạy các bài kiểm tra JUnit đó từ bảng điều khiển Dev UI. Trước tiên, bạn nên chuyển đến giao diện điều khiển Dev UI. Ở cuối trang, bạn sẽ tìm thấy mô-đun kiểm tra chịu trách nhiệm của bảng điều khiển. Chỉ cần nhấp vào biểu tượng Kết quả kiểm tra và bạn sẽ thấy một màn hình tương tự như bên dưới.

 

Mẹo 8. Biên dịch nguyên bản với GraalVM trên OpenShift

Bạn có thể dễ dàng tạo và chạy hình ảnh Quarkus GraalVM gốc trên OpenShift bằng cách sử dụng một lệnh và trình tạo duy nhất ubi-quarkus-native-s2i. OpenShift xây dựng ứng dụng bằng cách tiếp cận S2I (nguồn-2-hình ảnh). Tất nhiên, bạn chỉ cần một cụm OpenShift đang chạy (ví dụ CRC cục bộ hoặc Hộp cát nhà phát triển https://developers.redhat.com/products/codeready-containers/overview ) và ocứng dụng khách được cài đặt cục bộ.

$ 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

Mẹo 9. Giao dịch khôi phục sau mỗi lần thử nghiệm

Nếu bạn cần khôi phục các thay đổi trong dữ liệu sau mỗi lần kiểm tra, hãy tránh thực hiện theo cách thủ công. Thay vào đó, bạn chỉ cần chú thích lớp thử nghiệm của mình với @TestTransaction. Khôi phục được thực hiện mỗi khi phương pháp thử nghiệm hoàn tất.

@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);
    }
}

 

Mẹo 10. Tận dụng hỗ trợ GraphQL

Đó là mẹo cuối cùng của Quarkus trong bài viết này. Tuy nhiên, nó là một trong những tính năng Quarkus yêu thích của tôi. Hỗ trợ GraphQL không phải là một mặt mạnh của Spring Boot. Mặt khác, Quarkus cung cấp các phần mở rộng rất thú vị và đơn giản cho GraphQL cho phía máy khách và phía máy chủ.

Đầu tiên, hãy thêm các mô-đun Quarkus chịu trách nhiệm hỗ trợ 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>

Sau đó, chúng tôi có thể tạo một mã chịu trách nhiệm hiển thị API GraphQL. Lớp phải được chú thích với @GraphQLAPI. Quarkus tự động tạo lược đồ GraphQL từ mã nguồn.

@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);
    }

}

Sau đó, hãy tạo một giao diện khách để gọi hai điểm cuối. Chúng tôi cần chú thích giao diện đó với @GraphQLClientApi.

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

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

Cuối cùng, chúng ta có thể thêm một bài kiểm tra JUnit đơn giản. Chúng ta chỉ cần tiêm EmployeeClient, và sau đó gọi các phương thức. Nếu bạn quan tâm đến chi tiết hơn về hỗ trợ Quarkus GraphQL, hãy đọc bài viết của tôi An Advanced GraphQL với 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);
    }
}

Những Thougths cuối cùng

Theo tôi, Quarkus là một framework rất thú vị và đầy hứa hẹn. Với những thủ thuật này, bạn có thể dễ dàng bắt đầu phát triển ứng dụng đầu tiên của mình với Quarkus. Có một số tính năng thú vị mới trong mỗi bản phát hành mới. Vì vậy, có lẽ, tôi sẽ phải cập nhật danh sách các thủ thuật Quarkus này sớm

Liên kết: https://piotrminkowski.com/2021/10/12/quarkus-tips-tricks-and-techniques/

#quarkus #java #tips

10 Mẹo Và Thủ Thuật Hàng đầu Liên Quan đến Khuôn Khổ Quarkus

10 лучших советов и приемов, связанных с Quarkus Framework

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

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

Оглавление

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

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

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

$ 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

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

quarkus-советы-кли

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

$ quarkus dev

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

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

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

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

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

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

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

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

На следующем шаге мы собираемся определить класс репозитория. Поскольку он реализует 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, используемый в режимах 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

Перед запуском новой версии приложения мы должны включить зависимость H2 в Maven 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. Другие вещи, такие как, например, проверки работоспособности, обнаруживаются в исходном коде. Для этого нам нужно подключить 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

Предположим, у нас есть некоторая пользовательская конфигурация для установки в Deploymentманифесте. Наше приложение будет работать в двух модулях и автоматически будет отображаться за пределами кластера. Он также вводит значения 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<em> </em>quarkus:dev) вы можете получить доступ к консоли Dev UI по адресу http://localhost:8080/q/dev . Чем больше модулей вы включаете, тем больше параметров вы можете там настроить. Одна из моих любимых функций здесь — возможность развертывания приложений в OpenShift. Вместо запуска команды Maven для создания приложения мы можем просто запустить его в devрежиме и развернуть с помощью графического интерфейса.

quarkus-советы-dev-ui

 

Совет 7. Постоянно тестируйте

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

Итак, давайте добавим несколько тестов для нашего примера приложения. Во-первых, нам нужно включить тестовый модуль Quarkus и библиотеку REST Assured.

<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);
    }
}

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

 

Совет 8. Скомпилируйте с помощью GraalVM на OpenShift

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

$ 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, а затем вызвать методы. Если вас интересуют более подробные сведения о поддержке Quarkus GraphQL, прочитайте мою статью 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. В каждом новом релизе появляются новые интересные функции. Так что, возможно, мне скоро придется обновить этот список советов по Quarkus.

Ссылка: https://piotrminkowski.com/2021/10/12/quarkus-tips-tricks-and-techniques/

#quarkus #java #tips

10 лучших советов и приемов, связанных с Quarkus Framework
高橋  陽子

高橋 陽子

1659107406

如何在 Quarkus 中使用 Kafka 流

在本文中,您將學習如何將 Kafka Streams 與 Quarkus 一起使用。我們將使用 Quarkus 而不是 Spring Cloud。如果您想了解什麼是流媒體平台以及它與傳統消息代理有何不同,本文適合您。此外,我們將研究 Quarkus 提供的與 Apache Kafka 相關的有用改進。

建築學

在我們的例子中,有兩個傳入的事件流。它們都代表傳入的訂單。這些訂單由 order-service 應用程序生成。它向主題發送買單,向 orders.buy 主題發送賣單 orders.sell 。然後, stock-service 應用程序接收並處理傳入事件。第一步,它需要將每條消息的密鑰從 更改 orderId 為 productId。這是因為它必須連接來自與同一產品相關的不同主題的訂單才能執行交易。最後,交易價格是買賣價格的平均值。

quarkus-kafka-streams-拱

我們正在構建一個簡化版的股票市場平台。每個購買訂單都包含客戶期望購買產品的最高價格。另一方面,每個銷售訂單都包含客戶準備出售其產品的最低價格。如果特定產品的賣單價格不高於買單價格,我們將執行交易。

每個訂單的有效期為 10 秒。在那之後, stock-service 應用程序將不會處理這樣的訂單,因為它被認為是過期的。每個訂單包含許多用於交易的產品。例如,我們可能以 10 的價格賣出 100 或以 11 的價格買入 200。因此,訂單可能會全部或部分實現。stock-service 應用程序嘗試將部分實現的訂單連接到其他新的或部分實現的訂單。 您可以在下圖中看到該過程的可視化。

quarkus-kafka-streams 應用程序

在本地運行 Apache Kafka

在我們跳轉到實現之前,我們需要運行 Apache Kafka 的本地實例。如果您不想在筆記本電腦上安裝它,最好的運行方式是使用 Redpanda。Redpanda 是一個兼容 Kafka API 的流媒體平台。與 Kafka 相比,在本地運行它相對容易。通常,您必須在筆記本電腦上安裝 Redpanda,然後使用他們的 CLI 創建集群。但是有了 Quarkus,您就不需要這樣做了!唯一的要求是安裝 Docker。由於 Quarkus Kafka 擴展和名為 Dev Services 的功能,它會在開發模式和運行測試時自動啟動 Kafka 代理。此外,應用程序是自動配置的。

為了啟用該功能,您唯一需要做的就是不在配置屬性中提供任何 Kafka 地址。開發服務使用Testcontainers運行 Kafka,因此如果您有 Docker 或任何其他支持 Testcontainers 運行的環境,您將獲得開箱即用的 Kafka 容器化實例。另一個重要的事情。首先,啟動order-service應用程序。它會自動在 Kafka 中創建所有必需的主題。然後運行stock-service應用程序。它使用 Quarkus Kafka Streams 擴展並驗證是否存在所需的主題。讓我們形象化它。

quarkus-kafka-streams-運行

使用 Quarkus 向 Kafka 發送事件

有幾種方法可以使用 Quarkus 向 Kafka 發送事件。因為我們需要發送鍵/值對,所以我們將使用該io.smallrye.reactive.messaging.kafka.Record對象。Quarkus 能夠連續生成和發送數據。Order在下面可見的代碼片段中,我們每 500 毫秒發送一個事件。每個都Order包含一個隨機productId,priceproductCount

@Outgoing("orders-buy")
public Multi<Record<Long, Order>> buyOrdersGenerator() {
   return Multi.createFrom().ticks().every(Duration.ofMillis(500))
      .map(order -> {
         Integer productId = random.nextInt(10) + 1;
         int price = prices.get(productId) + random.nextInt(200);
         Order o = new Order(
             incrementOrderId(),
             random.nextInt(1000) + 1,
             productId,
             100 * (random.nextInt(5) + 1),
             LocalDateTime.now(),
             OrderType.BUY,
             price);
         log.infof("Sent: %s", o);
         return Record.of(o.getId(), o);
   });
}

@Outgoing("orders-sell")
public Multi<Record<Long, Order>> sellOrdersGenerator() {
   return Multi.createFrom().ticks().every(Duration.ofMillis(500))
      .map(order -> {
         Integer productId = random.nextInt(10) + 1;
         int price = prices.get(productId) + random.nextInt(200);
         Order o = new Order(
             incrementOrderId(),
             random.nextInt(1000) + 1,
             productId,
             100 * (random.nextInt(5) + 1),
             LocalDateTime.now(),
             OrderType.SELL,
             price);
         log.infof("Sent: %s", o);
         return Record.of(o.getId(), o);
   });
}

我們還將定義一個@Incoming通道以接收由stock-service. 由於 Quarkus 將自動創建transactionsQuarkus Kafka Streams 使用的主題stock-service。老實說,我無法強制 Quarkus Kafka Streams 擴展自動創建主題。看來我們需要為此使用 SmallRye Reactive Messaging 擴展。

@Incoming("transactions")
public void transactions(Transaction transaction) {
   log.infof("New: %s", transaction);
}

當然,我們需要在 Maven 中包含 SmallRye Reactive Messaging 依賴項pom.xml

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-reactive-messaging-kafka</artifactId>
</dependency>

最後,讓我們提供配置設置。我們有兩個傳出主題和一個傳入主題。我們可以設置他們的名字。否則,Quarkus 使用與通道名稱相同的名稱。我們的主題名稱是orders.buy和。order.selltransactions

mp.messaging.outgoing.orders-buy.connector = smallrye-kafka
mp.messaging.outgoing.orders-buy.topic = orders.buy
mp.messaging.outgoing.orders-buy.key.serializer = org.apache.kafka.common.serialization.LongSerializer
mp.messaging.outgoing.orders-buy.value.serializer = io.quarkus.kafka.client.serialization.ObjectMapperSerializer

mp.messaging.outgoing.orders-sell.connector = smallrye-kafka
mp.messaging.outgoing.orders-sell.topic = orders.sell
mp.messaging.outgoing.orders-sell.key.serializer = org.apache.kafka.common.serialization.LongSerializer
mp.messaging.outgoing.orders-sell.value.serializer = io.quarkus.kafka.client.serialization.ObjectMapperSerializer

mp.messaging.incoming.transactions.connector = smallrye-kafka
mp.messaging.incoming.transactions.topic = transactions
mp.messaging.incoming.transactions.value.deserializer = pl.piomin.samples.streams.order.model.deserializer.TransactionDeserializer

就這樣。我們的訂單生成器已準備就緒。如果您的order-service應用程序 Quarkus 也將運行 Kafka (Redpanda) 實例。但首先,讓我們切換到第二個示例應用程序 - stock-service.

使用 Quarkus 使用 Kafka 流

在上一節中,我們向 Kafka 代理髮送消息。因此,我們使用標準 Quarkus 庫與基於 SmallRye Reactive Messaging 框架的 Kafka 集成。該stock-service應用程序將消息作為流使用,因此現在我們將使用一個模塊來集成 Kafka Streams。

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-kafka-streams</artifactId>
</dependency>

我們的應用程序還使用一個數據庫、一個 ORM 層並包含一些其他有用的模塊。

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

第一步,我們將合併兩個訂單流(買入和賣出),將其 Order 插入數據庫,並打印事件消息。你可能會問——既然我有 Kafka,為什麼我在這裡使用數據庫和 ORM 層 KTable?好吧,我需要有鎖支持的事務來協調訂單實現的狀態(參考介紹中的描述——完全和部分實現的訂單)。我將在下一節中為您提供有關它的更多詳細信息。

為了使用 Quarkus 處理流,我們需要聲明org.apache.kafka.streams.Topologybean。它包含所有的KStreamKTable定義。讓我們從負責從傳入訂單創建和發出交易的部分開始。創建了兩個KStream定義。其中第一個負責將兩個訂單流合併為一個訂單流,然後將新訂單流插入Order數據庫。productId他們中的第二個通過使用密鑰連接兩個流來創建和執行事務。但更多關於它在下一節。

@Produces
public Topology buildTopology() {
   ObjectMapperSerde<Order> orderSerde = 
      new ObjectMapperSerde<>(Order.class);
   ObjectMapperSerde<Transaction> transactionSerde = 
      new ObjectMapperSerde<>(Transaction.class);

   StreamsBuilder builder = new StreamsBuilder();

   KStream<Long, Order> orders = builder.stream(
      ORDERS_SELL_TOPIC,
      Consumed.with(Serdes.Long(), orderSerde));

   builder.stream(ORDERS_BUY_TOPIC, 
         Consumed.with(Serdes.Long(), orderSerde))
      .merge(orders)
      .peek((k, v) -> {
         log.infof("New: %s", v);
         logic.add(v);
      });

   builder.stream(ORDERS_BUY_TOPIC, 
         Consumed.with(Serdes.Long(), orderSerde))
      .selectKey((k, v) -> v.getProductId())
      .join(orders.selectKey((k, v) -> v.getProductId()),
         this::execute,
         JoinWindows.of(Duration.ofSeconds(10)),
         StreamJoined.with(Serdes.Integer(), orderSerde, orderSerde))
      .filterNot((k, v) -> v == null)
      .map((k, v) -> new KeyValue<>(v.getId(), v))
      .peek((k, v) -> log.infof("Done -> %s", v))
      .to(TRANSACTIONS_TOPIC, Produced.with(Serdes.Long(), transactionSerde));

}

要處理流,我們需要添加配置屬性。需要輸入主題列表。我們還可以覆蓋默認應用程序 ID 並啟用 Kafka 健康檢查。

quarkus.kafka-streams.application-id = stock
quarkus.kafka-streams.topics = orders.buy,orders.sell
quarkus.kafka.health.enabled = true

Kafka Streams 上的操作

現在,我們可能會在 Kafka Streams 上使用一些更高級的操作,而不僅僅是合併兩個不同的流。事實上,這是我們應用程序中的一個關鍵邏輯。我們需要將兩個不同的訂單流連接成一個,使用 productId 作為連接鍵。由於生產者設置 orderId 為消息鍵,我們首先需要 為 和  流調用selectKey 方法 。在我們的案例中,加入與同一產品相關的買賣訂單只是第一步。然後我們需要驗證買單的最高價是否不大於賣單的最低價。order.sellorders.buy

下一步是驗證這兩個之前是否沒有實現,因為它們也可能與流中的其他訂單配對。如果所有條件都滿足,我們可以創建一個新的交易。最後,我們可以將流鍵從 productId 更改為 the transactionId 並將其發送到專用 transactions 主題。

每次我們成功加入兩個訂單時,我們都會嘗試創建一個交易。該execute(...)方法在方法內被調用KStream join。首先,我們比較兩個訂單的價格。然後我們通過訪問H2數據庫來驗證兩個訂單的實現狀態。如果訂單仍未完全實現,我們可能會創建一個事務並更新數據庫中的訂單記錄。

private Transaction execute(Order orderBuy, Order orderSell) {
   if (orderBuy.getAmount() >= orderSell.getAmount()) {
      int count = Math.min(orderBuy.getProductCount(), 
                           orderSell.getProductCount());
      boolean allowed = logic
         .performUpdate(orderBuy.getId(), orderSell.getId(), count);
      if (!allowed)
         return null;
      else
         return new Transaction(
            ++transactionId,
            orderBuy.getId(),
            orderSell.getId(),
            count,
            (orderBuy.getAmount() + orderSell.getAmount()) / 2,
            LocalDateTime.now(),
            "NEW"
      );
   } else {
            return null;
   }
}

讓我們仔細看看 performUpdate() 方法內部調用的 execute() 方法。它啟動一個事務並鎖定兩個 Order 實體。然後它會驗證每個訂單的實現狀態,並在可能的情況下使用當前值更新它。只有當 performUpdate() 方法成功完成時,stock-service應用程序才會創建一個新事務。

@ApplicationScoped
public class OrderLogic {

    @Inject
    Logger log;
    @Inject
    OrderRepository repository;

    @Transactional
    public Order add(Order order) {
        repository.persist(order);
        return order;
    }

    @Transactional
    public boolean performUpdate(Long buyOrderId, Long sellOrderId, int amount) {
        Order buyOrder = repository.findById(buyOrderId, 
           LockModeType.PESSIMISTIC_WRITE);
        Order sellOrder = repository.findById(sellOrderId, 
           LockModeType.PESSIMISTIC_WRITE);
        if (buyOrder == null || sellOrder == null)
            return false;
        int buyAvailableCount = 
           buyOrder.getProductCount() - buyOrder.getRealizedCount();
        int sellAvailableCount = 
           sellOrder.getProductCount() - sellOrder.getRealizedCount();
        if (buyAvailableCount >= amount && sellAvailableCount >= amount) {
            buyOrder.setRealizedCount(buyOrder.getRealizedCount() + amount);
            sellOrder.setRealizedCount(sellOrder.getRealizedCount() + amount);
            repository.persist(buyOrder);
            repository.persist(sellOrder);
            return true;
        } else {
            return false;
        }
    }
}

很好🙂這就是我們在練習的第一部分需要做的所有事情。現在我們可以運行我們的兩個示例應用程序。

使用 Quarkus 運行和管理 Kafka Streams 應用程序

正如我之前提到的,我們首先需要啟動order-service. 它運行一個新的 Kafka 實例並創建所有必需的主題。啟動後立即準備發送新訂單。要在本地運行 Quarkus 應用程序,只需轉到order-service目錄並執行以下命令:

$ mvn quarkus:dev

docker ps只是為了驗證您可以使用該命令顯示運行 Docker 容器的列表。這是我的結果:

如您所見,Redpanda 實例正在運行,並且可以在隨機端口上使用49724。Quarkus 為我們做到了。但是,如果您的筆記本電腦上安裝了 Redpanda,您可以使用 CLI 查看創建的主題列表rpk

$ rpk topic list --brokers=127.0.0.1:49724

然後讓我們運行stock-service. 轉到stock-service目錄並mvn quarkus:dev再次運行。啟動後,它只是工作。得益於 Quarkus 開發服務,這兩個應用程序共享同一個實例。現在讓我們訪問位於 的 Quarkus Dev UI 控制台http://localhost:8080/q/dev/。找到標題為“Apache Kafka Streams”的磁貼。

您可以查看我們的 Kafka Streams 拓撲的可視化。我將圖像分成兩部分以獲得更好的可見性。

將 Kafka KTable 與 Quarkus 一起使用

我們已經完成了負責從傳入訂單創建交易的邏輯的實現。在下一步中,我們將對事務流執行分析操作。price * productsCount我們的主要目標是計算每個產品的交易總數、售出/購買的產品總數和交易總值 ( )。這是計算中使用的對像類。

@RegisterForReflection
public class TransactionTotal {
   private int count;
   private int amount;
   private int productCount;

   // GETTERS AND SETTERS
}

因為該對 Transaction 像不包含有關產品的信息,所以我們首先需要加入訂單才能訪問它。然後我們 KTable 按每個 productId 分組和聚合生成一個。之後,我們可能會調用一個聚合方法,該方法允許我們執行一些更複雜的計算。在這種特殊情況下,我們正在計算所有已執行交易的數量、它們的產品數量和總價值。結果 KTable 可以物化為狀態存儲。多虧了這一點,我們將能夠通過TRANSACTIONS_PER_PRODUCT_SUMMARY變量定義的名稱來查詢它。

KeyValueBytesStoreSupplier storePerProductSupplier = Stores.persistentKeyValueStore(
   TRANSACTIONS_PER_PRODUCT_SUMMARY);

builder.stream(TRANSACTIONS_TOPIC, Consumed.with(Serdes.Long(), transactionSerde))
   .selectKey((k, v) -> v.getSellOrderId())
   .join(orders.selectKey((k, v) -> v.getId()),
      (t, o) -> new TransactionWithProduct(t, o.getProductId()),
      JoinWindows.of(Duration.ofSeconds(10)),
      StreamJoined.with(Serdes.Long(), transactionSerde, orderSerde))
   .groupBy((k, v) -> v.getProductId(), Grouped.with(Serdes.Integer(), transactionWithProductSerde))
   .aggregate(
      TransactionTotal::new,
      (k, v, a) -> {
         a.setCount(a.getCount() + 1);
         a.setProductCount(a.getAmount() + v.getTransaction().getAmount());
         a.setAmount(a.getProductCount() +
            (v.getTransaction().getAmount() * v.getTransaction().getPrice()));
         return a;
      },
      Materialized.<Integer, TransactionTotal> as(storePerProductSupplier)
         .withKeySerde(Serdes.Integer())
         .withValueSerde(transactionTotalSerde))
   .toStream()
   .peek((k, v) -> log.infof("Total per product(%d): %s", k, v))
   .to(TRANSACTIONS_PER_PRODUCT_AGGREGATED_TOPIC, 
      Produced.with(Serdes.Integer(), transactionTotalSerde));

這是負責交互式查詢實現的類。它注入KafkaStreamsbean。然後它嘗試根據StockService.TRANSACTIONS_PER_PRODUCT_SUMMARY變量獲取持久存儲。結果,有一個ReadOnlyKeyValueStorewithInteger作為鍵和TransactionTotal一個值。我們可能會返回與特定產品相關的單個值productId( getTransactionsPerProductData) 或僅返回包含所有可用產品結果的列表 ( getAllTransactionsPerProductData)。

@ApplicationScoped
public class InteractiveQueries {

   @Inject
   KafkaStreams streams;

   public TransactionTotal getTransactionsPerProductData(Integer productId) {
      return getTransactionsPerProductStore().get(productId);
   }

   public Map<Integer, TransactionTotal> getAllTransactionsPerProductData() {
      Map<Integer, TransactionTotal> m = new HashMap<>();
      KeyValueIterator<Integer, TransactionTotal> it = getTransactionsPerProductStore().all();
      while (it.hasNext()) {
         KeyValue<Integer, TransactionTotal> kv = it.next();
         m.put(kv.key, kv.value);
      }
      return m;
   }

   private ReadOnlyKeyValueStore<Integer, TransactionTotal> getTransactionsPerProductStore() {
      return streams.store(
         StoreQueryParameters
            .fromNameAndType(StockService.TRANSACTIONS_PER_PRODUCT_SUMMARY, QueryableStoreTypes.keyValueStore()));
   }

}

最後,我們可以創建一個 REST 控制器,負責公開交互式查詢檢索到的數據。

@ApplicationScoped
@Path("/transactions")
public class TransactionResource {

    @Inject
    InteractiveQueries interactiveQueries;

    @GET
    @Path("/products/{id}")
    public TransactionTotal getByProductId(@PathParam("id") Integer productId) {
        return interactiveQueries.getTransactionsPerProductData(productId);
    }

    @GET
    @Path("/products")
    public Map<Integer, TransactionTotal> getAllPerProductId() {
        return interactiveQueries.getAllTransactionsPerProductData();
    }

}

現在您可以輕鬆查看與stock-service. 您只需要調用以下 REST 端點,例如:

$ curl http://localhost:8080/transactions/products
$ curl http://localhost:8080/transactions/products/3
$ curl http://localhost:8080/transactions/products/5

最後的想法

Quarkus 簡化了 Kafka Streams 和交互式查詢的工作。它為開發人員提供了有用的改進,例如在開發和測試模式下自動啟動 Kafka 或在開發 UI 控制台中可視化 Kafka 流。您可以輕鬆地將 Quarkus 方法與 Spring Cloud Stream Kafka 支持進行比較,因為我為這兩個框架實現了相同的邏輯。這是帶有 Spring Cloud Stream Kafka Streams 示例的 GitHub存儲庫。

鏈接:https ://piotrminkowski.com/2021/11/24/kafka-streams-with-quarkus/

#quarkus #java #kafka 

如何在 Quarkus 中使用 Kafka 流
Thierry  Perret

Thierry Perret

1659100165

Comment Utiliser Les Flux Kafka Avec Quarkus

Dans cet article, vous apprendrez à utiliser Kafka Streams avec Quarkus. Nous allons utiliser Quarkus au lieu de Spring Cloud. Si vous souhaitez comprendre ce qu'est une plateforme de streaming et en quoi elle diffère d'un courtier de messages traditionnel, cet article est pour vous. De plus, nous étudierons des améliorations utiles liées à Apache Kafka fournies par Quarkus.

Architecture

Dans notre cas, il y a deux flux entrants d'événements. Les deux représentent les commandes entrantes. Ces commandes sont générées par l'  order-service application. Il envoie des ordres d'achat au  orders.buy sujet et des ordres de vente au  orders.sell sujet. Ensuite, l'  stock-service application reçoit et gère les événements entrants. Dans un premier temps, il doit changer la clé de chaque message du  orderId au  productId. En effet, il doit joindre des commandes de différents sujets liés au même produit afin d'exécuter des transactions. Enfin, le prix de transaction est une moyenne des prix de vente et d'achat.

quarkus-kafka-streams-arch

Nous construisons une version simplifiée de la plateforme boursière. Chaque ordre d'achat contient un prix maximum auquel un client s'attend à acheter un produit. D'autre part, chaque ordre de vente contient un prix minimum auquel un client est prêt à vendre son produit. Si le prix de l'ordre de vente n'est pas supérieur au prix de l'ordre d'achat pour un produit particulier, nous effectuons une transaction.

Chaque commande est valable 10 secondes. Passé ce délai, l'  stock-service application ne traitera pas une telle commande puisqu'elle est considérée comme expirée. Chaque commande contient un certain nombre de produits pour une transaction. Par exemple, nous pouvons vendre 100 pour 10 ou acheter 200 pour 11. Par conséquent, un ordre peut être entièrement ou partiellement réalisé. L'  stock-service application essaie de joindre des commandes partiellement réalisées à d'autres commandes nouvelles ou partiellement réalisées. Vous pouvez voir la visualisation de ce processus dans l'image ci-dessous.

application-quarkus-kafka-streams

Exécutez Apache Kafka localement

Avant de passer à l'implémentation, nous devons exécuter une instance locale d'Apache Kafka. Si vous ne souhaitez pas l'installer sur votre ordinateur portable, la meilleure façon de l'exécuter est avec  Redpanda . Redpanda est une plateforme de streaming compatible avec l'API Kafka. Par rapport à Kafka, il est relativement facile de l'exécuter localement. Normalement, vous devez installer Redpanda sur votre ordinateur portable, puis créer un cluster à l'aide de leur CLI. Mais avec Quarkus, vous n'avez pas besoin de faire cela ! La seule exigence est d'avoir Docker installé. Grâce à l'extension Quarkus Kafka et à la fonctionnalité appelée Dev Services, il démarre automatiquement un courtier Kafka en mode dev et lors de l'exécution de tests. De plus, l'application est configurée automatiquement.

La seule chose que vous devez faire pour activer cette fonctionnalité est de NE PAS fournir d'adresse Kafka dans les propriétés de configuration. Dev Services utilise Testcontainers pour exécuter Kafka, donc si vous avez Docker ou tout autre environnement prenant en charge Testcontainers en cours d'exécution, vous obtenez une instance conteneurisée de Kafka prête à l'emploi. Une autre chose importante. Tout d'abord, lancez l' order-serviceapplication. Il crée automatiquement tous les sujets requis dans Kafka. Exécutez ensuite l' stock-serviceapplication. Il utilise l'extension Quarkus Kafka Streams et vérifie si les rubriques requises existent. Visualisons-le.

quarkus-kafka-streams-run

Envoyer des événements à Kafka avec Quarkus

Il existe plusieurs façons d'envoyer des événements à Kafka avec Quarkus. Parce que nous devons envoyer une paire clé/valeur, nous utiliserons l' io.smallrye.reactive.messaging.kafka.Recordobjet pour cela. Quarkus est capable de générer et d'envoyer des données en continu. Dans le fragment de code visible ci-dessous, nous envoyons un seul Orderévénement toutes les 500 ms. Chacun Ordercontient un aléatoire productId, priceet productCount.

@Outgoing("orders-buy")
public Multi<Record<Long, Order>> buyOrdersGenerator() {
   return Multi.createFrom().ticks().every(Duration.ofMillis(500))
      .map(order -> {
         Integer productId = random.nextInt(10) + 1;
         int price = prices.get(productId) + random.nextInt(200);
         Order o = new Order(
             incrementOrderId(),
             random.nextInt(1000) + 1,
             productId,
             100 * (random.nextInt(5) + 1),
             LocalDateTime.now(),
             OrderType.BUY,
             price);
         log.infof("Sent: %s", o);
         return Record.of(o.getId(), o);
   });
}

@Outgoing("orders-sell")
public Multi<Record<Long, Order>> sellOrdersGenerator() {
   return Multi.createFrom().ticks().every(Duration.ofMillis(500))
      .map(order -> {
         Integer productId = random.nextInt(10) + 1;
         int price = prices.get(productId) + random.nextInt(200);
         Order o = new Order(
             incrementOrderId(),
             random.nextInt(1000) + 1,
             productId,
             100 * (random.nextInt(5) + 1),
             LocalDateTime.now(),
             OrderType.SELL,
             price);
         log.infof("Sent: %s", o);
         return Record.of(o.getId(), o);
   });
}

Nous définirons également un @Incomingcanal unique afin de recevoir les transactions produites par le stock-service. Grâce à cela, Quarkus créera automatiquement le sujet transactionsutilisé par Quarkus Kafka Streams dans stock-service. Pour être honnête, je n'ai pas pu forcer l'extension Quarkus Kafka Streams à créer le sujet automatiquement. Il semble que nous devions utiliser l'extension SmallRye Reactive Messaging pour cela.

@Incoming("transactions")
public void transactions(Transaction transaction) {
   log.infof("New: %s", transaction);
}

Bien sûr, nous devons inclure la dépendance SmallRye Reactive Messaging au Maven pom.xml.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-reactive-messaging-kafka</artifactId>
</dependency>

Enfin, fournissons les paramètres de configuration. Nous avons deux sujets sortants et un seul sujet entrant. Nous pouvons définir leurs noms. Sinon, Quarkus utilise le même nom que le nom du canal. Les noms de nos sujets sont orders.buy, order.sellet transactions.

mp.messaging.outgoing.orders-buy.connector = smallrye-kafka
mp.messaging.outgoing.orders-buy.topic = orders.buy
mp.messaging.outgoing.orders-buy.key.serializer = org.apache.kafka.common.serialization.LongSerializer
mp.messaging.outgoing.orders-buy.value.serializer = io.quarkus.kafka.client.serialization.ObjectMapperSerializer

mp.messaging.outgoing.orders-sell.connector = smallrye-kafka
mp.messaging.outgoing.orders-sell.topic = orders.sell
mp.messaging.outgoing.orders-sell.key.serializer = org.apache.kafka.common.serialization.LongSerializer
mp.messaging.outgoing.orders-sell.value.serializer = io.quarkus.kafka.client.serialization.ObjectMapperSerializer

mp.messaging.incoming.transactions.connector = smallrye-kafka
mp.messaging.incoming.transactions.topic = transactions
mp.messaging.incoming.transactions.value.deserializer = pl.piomin.samples.streams.order.model.deserializer.TransactionDeserializer

C'est tout. Notre générateur de commandes est prêt. Si vous utilisez l' order-serviceapplication, Quarkus exécutera également l'instance Kafka (Redpanda). Mais d'abord, passons au deuxième exemple d'application - stock-service.

Consommer des flux Kafka avec Quarkus

Dans la section précédente, nous envoyions des messages au courtier Kafka. Par conséquent, nous avons utilisé une bibliothèque Quarkus standard pour l'intégration avec Kafka basée sur le framework SmallRye Reactive Messaging. L' stock-serviceapplication consomme des messages sous forme de flux, nous allons donc maintenant utiliser un module pour l'intégration de Kafka Streams.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-kafka-streams</artifactId>
</dependency>

Notre application utilise également une base de données, une couche ORM et inclut quelques autres modules utiles.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

Dans un premier temps, nous allons fusionner les deux flux d'ordres (achat et vente), insérer le  Order dans la base de données et imprimer le message d'événement. Vous pourriez demander - pourquoi j'utilise la base de données et la couche ORM ici puisque j'ai Kafka  KTable? Eh bien, j'ai besoin de transactions avec support de verrouillage afin de coordonner l'état de réalisation de la commande (reportez-vous à la description dans l'introduction - commandes entièrement et partiellement réalisées). Je vous donnerai plus de détails à ce sujet dans les sections suivantes.

Afin de traiter les flux avec Quarkus, nous devons déclarer le org.apache.kafka.streams.Topologybean. Il contient toutes les définitions KStreamet . KTableCommençons par la partie responsable de la création et de l'émission des transactions à partir des commandes entrantes. Deux KStreamdéfinitions sont créées. Le premier d'entre eux est chargé de fusionner deux flux de commandes en un seul, puis d'en insérer un nouveau Orderdans une base de données. Le second d'entre eux crée et exécute des transactions en joignant deux flux à l'aide de la productIdclé. Mais plus à ce sujet dans la section suivante.

@Produces
public Topology buildTopology() {
   ObjectMapperSerde<Order> orderSerde = 
      new ObjectMapperSerde<>(Order.class);
   ObjectMapperSerde<Transaction> transactionSerde = 
      new ObjectMapperSerde<>(Transaction.class);

   StreamsBuilder builder = new StreamsBuilder();

   KStream<Long, Order> orders = builder.stream(
      ORDERS_SELL_TOPIC,
      Consumed.with(Serdes.Long(), orderSerde));

   builder.stream(ORDERS_BUY_TOPIC, 
         Consumed.with(Serdes.Long(), orderSerde))
      .merge(orders)
      .peek((k, v) -> {
         log.infof("New: %s", v);
         logic.add(v);
      });

   builder.stream(ORDERS_BUY_TOPIC, 
         Consumed.with(Serdes.Long(), orderSerde))
      .selectKey((k, v) -> v.getProductId())
      .join(orders.selectKey((k, v) -> v.getProductId()),
         this::execute,
         JoinWindows.of(Duration.ofSeconds(10)),
         StreamJoined.with(Serdes.Integer(), orderSerde, orderSerde))
      .filterNot((k, v) -> v == null)
      .map((k, v) -> new KeyValue<>(v.getId(), v))
      .peek((k, v) -> log.infof("Done -> %s", v))
      .to(TRANSACTIONS_TOPIC, Produced.with(Serdes.Long(), transactionSerde));

}

Pour traiter les flux, nous devons ajouter des propriétés de configuration. Une liste de sujets d'entrée est requise. Nous pouvons également remplacer un identifiant d'application par défaut et activer la vérification de l'état de Kafka.

quarkus.kafka-streams.application-id = stock
quarkus.kafka-streams.topics = orders.buy,orders.sell
quarkus.kafka.health.enabled = true

Opérations sur les flux Kafka

Maintenant, nous pouvons utiliser des opérations plus avancées sur Kafka Streams que la simple fusion de deux flux différents. En fait, c'est une logique clé dans notre application. Nous devons joindre deux flux de commandes différents en un seul en utilisant le  productId comme clé de jonction. Étant donné que le producteur définit  orderId une clé de message, nous devons d'abord appeler la  selectKey méthode pour les   flux order.sell et  . orders.buyDans notre cas, joindre des ordres d'achat et de vente liés au même produit n'est qu'une première étape. Ensuite, nous devons vérifier si le prix maximum dans l'ordre d'achat n'est pas supérieur au prix minimum dans l'ordre de vente.

L'étape suivante consiste à vérifier si ces deux éléments n'ont pas été réalisés auparavant, car ils peuvent également être associés à d'autres commandes dans le flux. Si toutes les conditions sont remplies, nous pouvons créer une nouvelle transaction. Enfin, nous pouvons changer une clé de flux de productId en the  transactionId et l'envoyer au sujet dédié  transactions .

Chaque fois que nous réussissons à joindre deux commandes, nous essayons de créer une transaction. La execute(...)méthode est appelée dans la KStream joinméthode. Tout d'abord, nous comparons les prix des deux commandes. Ensuite, nous vérifions l'état de réalisation des deux commandes en accédant à la base de données H2. Si les commandes ne sont toujours pas entièrement réalisées, nous pouvons créer une transaction et mettre à jour les enregistrements de commandes dans la base de données.

private Transaction execute(Order orderBuy, Order orderSell) {
   if (orderBuy.getAmount() >= orderSell.getAmount()) {
      int count = Math.min(orderBuy.getProductCount(), 
                           orderSell.getProductCount());
      boolean allowed = logic
         .performUpdate(orderBuy.getId(), orderSell.getId(), count);
      if (!allowed)
         return null;
      else
         return new Transaction(
            ++transactionId,
            orderBuy.getId(),
            orderSell.getId(),
            count,
            (orderBuy.getAmount() + orderSell.getAmount()) / 2,
            LocalDateTime.now(),
            "NEW"
      );
   } else {
            return null;
   }
}

Examinons de plus près la  performUpdate() méthode appelée à l'intérieur de la  execute() méthode. Il initie une transaction et verrouille les deux  Order entités. Ensuite, il vérifie l'état de réalisation de chaque commande et le met à jour avec les valeurs actuelles si possible. Ce n'est que si la  performUpdate() méthode se termine avec succès que l' stock-serviceapplication crée une nouvelle transaction.

@ApplicationScoped
public class OrderLogic {

    @Inject
    Logger log;
    @Inject
    OrderRepository repository;

    @Transactional
    public Order add(Order order) {
        repository.persist(order);
        return order;
    }

    @Transactional
    public boolean performUpdate(Long buyOrderId, Long sellOrderId, int amount) {
        Order buyOrder = repository.findById(buyOrderId, 
           LockModeType.PESSIMISTIC_WRITE);
        Order sellOrder = repository.findById(sellOrderId, 
           LockModeType.PESSIMISTIC_WRITE);
        if (buyOrder == null || sellOrder == null)
            return false;
        int buyAvailableCount = 
           buyOrder.getProductCount() - buyOrder.getRealizedCount();
        int sellAvailableCount = 
           sellOrder.getProductCount() - sellOrder.getRealizedCount();
        if (buyAvailableCount >= amount && sellAvailableCount >= amount) {
            buyOrder.setRealizedCount(buyOrder.getRealizedCount() + amount);
            sellOrder.setRealizedCount(sellOrder.getRealizedCount() + amount);
            repository.persist(buyOrder);
            repository.persist(sellOrder);
            return true;
        } else {
            return false;
        }
    }
}

Nice🙂C'est tout ce que nous devons faire dans la première partie de notre exercice. Nous pouvons maintenant exécuter nos deux exemples d'applications.

Exécutez et gérez l'application Kafka Streams avec Quarkus

Comme je l'ai mentionné précédemment, nous devons d'abord démarrer le fichier order-service. Il exécute une nouvelle instance Kafka et crée tous les sujets requis. Immédiatement après le démarrage, il est prêt à envoyer de nouvelles commandes. Pour exécuter l'application Quarkus localement, accédez simplement au order-servicerépertoire et exécutez la commande suivante :

$ mvn quarkus:dev

Juste pour vérifier que vous pouvez afficher une liste exécutant des conteneurs Docker avec la docker pscommande. Voici mon résultat :

Comme vous le voyez, l'instance de Redpanda est en cours d'exécution et elle est disponible sur un port aléatoire 49724. Quarkus l'a fait pour nous. Cependant, si Redpanda est installé sur votre ordinateur portable, vous consultez la liste des sujets créés avec leur CLI rpk:

$ rpk topic list --brokers=127.0.0.1:49724

Alors lançons le stock-service. Allez dans le stock-servicerépertoire et exécutez mvn quarkus:devà nouveau. Après le démarrage, ça marche. Les deux applications partagent la même instance grâce aux Quarkus Dev Services. Accédons maintenant à la console Quarkus Dev UI disponible sur http://localhost:8080/q/dev/. Trouvez la tuile avec le titre "Apache Kafka Streams" .

Vous pouvez consulter une visualisation de notre topologie Kafka Streams. Je vais diviser l'image en deux parties pour une meilleure visibilité.

Utiliser Kafka KTable avec Quarkus

Nous avons déjà terminé l'implémentation de la logique responsable de la création des transactions à partir des commandes entrantes. Dans l'étape suivante, nous allons effectuer des opérations analytiques sur le flux de transactions. Notre objectif principal est de calculer le nombre total de transactions, le nombre total de produits vendus/achetés et la valeur totale des transactions ( price * productsCount) pour chaque produit. Voici la classe d'objet utilisée dans les calculs.

@RegisterForReflection
public class TransactionTotal {
   private int count;
   private int amount;
   private int productCount;

   // GETTERS AND SETTERS
}

Étant donné que l'  Transaction objet ne contient pas d'informations sur le produit, nous devons d'abord joindre la commande pour y accéder. Ensuite, nous produisons un   groupement et une agrégation KTable par par  . productIdAprès cela, nous pouvons invoquer une méthode d'agrégation qui nous permet d'effectuer des calculs plus complexes. Dans ce cas particulier, nous calculons le nombre de toutes les transactions exécutées, leur volume de produits et leur valeur totale. Le résultat  KTable peut être matérialisé en tant que magasin d'état. Grâce à cela nous pourrons l'interroger par le nom défini par la TRANSACTIONS_PER_PRODUCT_SUMMARYvariable.

KeyValueBytesStoreSupplier storePerProductSupplier = Stores.persistentKeyValueStore(
   TRANSACTIONS_PER_PRODUCT_SUMMARY);

builder.stream(TRANSACTIONS_TOPIC, Consumed.with(Serdes.Long(), transactionSerde))
   .selectKey((k, v) -> v.getSellOrderId())
   .join(orders.selectKey((k, v) -> v.getId()),
      (t, o) -> new TransactionWithProduct(t, o.getProductId()),
      JoinWindows.of(Duration.ofSeconds(10)),
      StreamJoined.with(Serdes.Long(), transactionSerde, orderSerde))
   .groupBy((k, v) -> v.getProductId(), Grouped.with(Serdes.Integer(), transactionWithProductSerde))
   .aggregate(
      TransactionTotal::new,
      (k, v, a) -> {
         a.setCount(a.getCount() + 1);
         a.setProductCount(a.getAmount() + v.getTransaction().getAmount());
         a.setAmount(a.getProductCount() +
            (v.getTransaction().getAmount() * v.getTransaction().getPrice()));
         return a;
      },
      Materialized.<Integer, TransactionTotal> as(storePerProductSupplier)
         .withKeySerde(Serdes.Integer())
         .withValueSerde(transactionTotalSerde))
   .toStream()
   .peek((k, v) -> log.infof("Total per product(%d): %s", k, v))
   .to(TRANSACTIONS_PER_PRODUCT_AGGREGATED_TOPIC, 
      Produced.with(Serdes.Integer(), transactionTotalSerde));

Voici la classe responsable de l'implémentation des requêtes interactives. Il injecte du KafkaStreamsharicot. Ensuite, il essaie d'obtenir un magasin persistant en se basant sur la StockService.TRANSACTIONS_PER_PRODUCT_SUMMARYvariable. Il en résulte un ReadOnlyKeyValueStoreavec Integercomme clé, et TransactionTotalcomme valeur. Nous pouvons renvoyer une seule valeur liée au particulier productId( getTransactionsPerProductData) ou simplement renvoyer une liste avec les résultats pour tous les produits disponibles ( getAllTransactionsPerProductData).

@ApplicationScoped
public class InteractiveQueries {

   @Inject
   KafkaStreams streams;

   public TransactionTotal getTransactionsPerProductData(Integer productId) {
      return getTransactionsPerProductStore().get(productId);
   }

   public Map<Integer, TransactionTotal> getAllTransactionsPerProductData() {
      Map<Integer, TransactionTotal> m = new HashMap<>();
      KeyValueIterator<Integer, TransactionTotal> it = getTransactionsPerProductStore().all();
      while (it.hasNext()) {
         KeyValue<Integer, TransactionTotal> kv = it.next();
         m.put(kv.key, kv.value);
      }
      return m;
   }

   private ReadOnlyKeyValueStore<Integer, TransactionTotal> getTransactionsPerProductStore() {
      return streams.store(
         StoreQueryParameters
            .fromNameAndType(StockService.TRANSACTIONS_PER_PRODUCT_SUMMARY, QueryableStoreTypes.keyValueStore()));
   }

}

Enfin, nous pouvons créer un contrôleur REST chargé d'exposer les données récupérées par les requêtes interactives.

@ApplicationScoped
@Path("/transactions")
public class TransactionResource {

    @Inject
    InteractiveQueries interactiveQueries;

    @GET
    @Path("/products/{id}")
    public TransactionTotal getByProductId(@PathParam("id") Integer productId) {
        return interactiveQueries.getTransactionsPerProductData(productId);
    }

    @GET
    @Path("/products")
    public Map<Integer, TransactionTotal> getAllPerProductId() {
        return interactiveQueries.getAllTransactionsPerProductData();
    }

}

Vous pouvez désormais consulter facilement les statistiques relatives aux transactions créées par le stock-service. Il vous suffit d'appeler les points de terminaison REST suivants, par exemple :

$ curl http://localhost:8080/transactions/products
$ curl http://localhost:8080/transactions/products/3
$ curl http://localhost:8080/transactions/products/5

Dernières pensées

Quarkus simplifie le travail avec Kafka Streams et les requêtes interactives. Il fournit des améliorations utiles pour les développeurs, telles que le démarrage automatique de Kafka dans les modes de développement et de test ou la visualisation des flux Kafka dans la console de développement de l'interface utilisateur. Vous pouvez facilement comparer l'approche Quarkus avec la prise en charge de Spring Cloud Stream Kafka puisque j'ai implémenté la même logique pour ces deux frameworks. Voici le référentiel GitHub avec l'exemple Spring Cloud Stream Kafka Streams.

Lien : https://piotrminkowski.com/2021/11/24/kafka-streams-with-quarkus/

#quarkus #java #kafka

Comment Utiliser Les Flux Kafka Avec Quarkus
Hans  Marvin

Hans Marvin

1659092940

How to Use Kafka Streams with Quarkus

In this article, you will learn how to use Kafka Streams with Quarkus. We are going to use Quarkus instead of Spring Cloud. If you would like to figure out what is a streaming platform and how it differs from a traditional message broker this article is for you. Moreover, we will study useful improvements related to Apache Kafka provided by Quarkus.

See more at: https://piotrminkowski.com/2021/11/24/kafka-streams-with-quarkus/

#quarkus #java #kafka 

How to Use Kafka Streams with Quarkus
Hoang  Kim

Hoang Kim

1659085680

Cách Sử Dụng Suối Kafka Với Quarkus

Trong bài viết này, bạn sẽ học cách sử dụng Kafka Streams với Quarkus. Chúng tôi sẽ sử dụng Quarkus thay vì Spring Cloud. Nếu bạn muốn tìm hiểu một nền tảng phát trực tuyến là gì và nó khác với một nhà môi giới tin nhắn truyền thống như thế nào thì bài viết này là dành cho bạn. Hơn nữa, chúng tôi sẽ nghiên cứu những cải tiến hữu ích liên quan đến Apache Kafka do Quarkus cung cấp.

Ngành kiến ​​​​trúc

Trong trường hợp của chúng ta, có hai luồng sự kiện đến. Cả hai đều đại diện cho các đơn đặt hàng đến. Các đơn đặt hàng này được tạo ra bởi  order-service ứng dụng. Nó gửi lệnh mua đến  orders.buy chủ đề và lệnh bán cho  orders.sell chủ đề. Sau đó,  stock-service ứng dụng nhận và xử lý các sự kiện đến. Trong bước đầu tiên, nó cần thay đổi khóa của mỗi thông báo từ  orderId đến  productId. Đó là bởi vì nó phải tham gia các đơn đặt hàng từ các chủ đề khác nhau liên quan đến cùng một sản phẩm để thực hiện giao dịch. Cuối cùng, giá giao dịch là giá trung bình của giá mua và bán.

quarkus-kafka-Stream-Arch

Chúng tôi đang xây dựng một phiên bản đơn giản hóa của nền tảng thị trường chứng khoán. Mỗi lệnh mua chứa một mức giá tối đa mà khách hàng đang mong đợi để mua một sản phẩm. Mặt khác, mỗi đơn đặt hàng có một mức giá tối thiểu mà khách hàng sẵn sàng bán sản phẩm của mình. Nếu giá đặt hàng bán không lớn hơn giá đặt hàng mua cho một sản phẩm cụ thể, chúng tôi đang thực hiện giao dịch.

Mỗi đơn hàng có giá trị trong 10 giây. Sau thời gian đó,  stock-service ứng dụng sẽ không xử lý đơn đặt hàng như vậy vì nó được coi là đã hết hạn. Mỗi đơn đặt hàng chứa một số sản phẩm cho một giao dịch. Ví dụ, chúng ta có thể bán 100 với giá 10 hoặc mua 200 với giá 11. Do đó, một đơn đặt hàng có thể được thực hiện toàn bộ hoặc một phần. Ứng  stock-service dụng cố gắng nối các đơn hàng đã thực hiện một phần với các đơn hàng mới hoặc đã thực hiện một phần khác. Bạn có thể xem hình dung của quá trình đó trong hình dưới đây.

quarkus-kafka-stream-app

Chạy Apache Kafka cục bộ

Trước khi bắt đầu triển khai, chúng ta cần chạy một phiên bản cục bộ của Apache Kafka. Nếu bạn không muốn cài đặt nó trên máy tính xách tay của mình, cách tốt nhất để chạy nó là với  Redpanda . Redpanda là một nền tảng phát trực tuyến tương thích với API Kafka. So với Kafka, nó tương đối dễ dàng để chạy nó cục bộ. Thông thường, bạn sẽ phải cài đặt Redpanda trên máy tính xách tay của mình và sau đó tạo một cụm bằng CLI của chúng. Nhưng với Quarkus bạn không cần phải làm vậy! Yêu cầu duy nhất là phải cài đặt Docker. Nhờ có tiện ích mở rộng Quarkus Kafka và tính năng được gọi là Dịch vụ Dev, nó sẽ tự động khởi động nhà môi giới Kafka ở chế độ nhà phát triển và khi chạy thử nghiệm. Hơn nữa, ứng dụng được cấu hình tự động.

Điều duy nhất bạn cần làm để kích hoạt tính năng đó là KHÔNG cung cấp bất kỳ địa chỉ Kafka nào trong thuộc tính cấu hình. Dev Services sử dụng Testcontainers để chạy Kafka, vì vậy nếu bạn có Docker hoặc bất kỳ môi trường nào khác hỗ trợ Testcontainers chạy, bạn sẽ nhận được một phiên bản Kafka có sẵn trong hộp. Một điều quan trọng khác. Đầu tiên, khởi động order-serviceứng dụng. Nó tự động tạo tất cả các chủ đề được yêu cầu trong Kafka. Sau đó chạy stock-serviceứng dụng. Nó sử dụng tiện ích mở rộng Quarkus Kafka Streams và xác minh xem các chủ đề được yêu cầu có tồn tại hay không. Hãy hình dung nó.

quarkus-kafka-stream-run

Gửi sự kiện cho Kafka với Quarkus

Có một số cách để gửi sự kiện đến Kafka bằng Quarkus. Bởi vì chúng tôi cần gửi cặp khóa / giá trị, chúng tôi sẽ sử dụng io.smallrye.reactive.messaging.kafka.Recordđối tượng cho việc đó. Quarkus có thể tạo và gửi dữ liệu liên tục. Trong đoạn mã hiển thị bên dưới, chúng tôi gửi một Ordersự kiện duy nhất trong mỗi 500 mili giây. Mỗi Orderchứa một ngẫu nhiên productId, priceproductCount.

@Outgoing("orders-buy")
public Multi<Record<Long, Order>> buyOrdersGenerator() {
   return Multi.createFrom().ticks().every(Duration.ofMillis(500))
      .map(order -> {
         Integer productId = random.nextInt(10) + 1;
         int price = prices.get(productId) + random.nextInt(200);
         Order o = new Order(
             incrementOrderId(),
             random.nextInt(1000) + 1,
             productId,
             100 * (random.nextInt(5) + 1),
             LocalDateTime.now(),
             OrderType.BUY,
             price);
         log.infof("Sent: %s", o);
         return Record.of(o.getId(), o);
   });
}

@Outgoing("orders-sell")
public Multi<Record<Long, Order>> sellOrdersGenerator() {
   return Multi.createFrom().ticks().every(Duration.ofMillis(500))
      .map(order -> {
         Integer productId = random.nextInt(10) + 1;
         int price = prices.get(productId) + random.nextInt(200);
         Order o = new Order(
             incrementOrderId(),
             random.nextInt(1000) + 1,
             productId,
             100 * (random.nextInt(5) + 1),
             LocalDateTime.now(),
             OrderType.SELL,
             price);
         log.infof("Sent: %s", o);
         return Record.of(o.getId(), o);
   });
}

Chúng tôi cũng sẽ xác định một @Incomingkênh duy nhất để nhận các giao dịch do stock-service. Nhờ đó Quarkus sẽ tự động tạo chủ đề transactionsđược Quarkus Kafka Streams sử dụng trong stock-service. Thành thật mà nói, tôi không thể buộc phần mở rộng Quarkus Kafka Streams tự động tạo chủ đề. Có vẻ như chúng ta cần sử dụng tiện ích Nhắn tin phản ứng SmallRye cho việc đó.

@Incoming("transactions")
public void transactions(Transaction transaction) {
   log.infof("New: %s", transaction);
}

Tất nhiên, chúng ta cần bao gồm sự phụ thuộc của SmallRye Reactive Messaging vào Maven pom.xml.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-reactive-messaging-kafka</artifactId>
</dependency>

Cuối cùng, hãy cung cấp cài đặt cấu hình. Chúng tôi có hai chủ đề gửi đi và một chủ đề đến. Chúng tôi có thể đặt tên của họ. Nếu không, Quarkus sử dụng cùng tên với tên của kênh. Tên của các chủ đề của chúng tôi là orders.buy, order.selltransactions.

mp.messaging.outgoing.orders-buy.connector = smallrye-kafka
mp.messaging.outgoing.orders-buy.topic = orders.buy
mp.messaging.outgoing.orders-buy.key.serializer = org.apache.kafka.common.serialization.LongSerializer
mp.messaging.outgoing.orders-buy.value.serializer = io.quarkus.kafka.client.serialization.ObjectMapperSerializer

mp.messaging.outgoing.orders-sell.connector = smallrye-kafka
mp.messaging.outgoing.orders-sell.topic = orders.sell
mp.messaging.outgoing.orders-sell.key.serializer = org.apache.kafka.common.serialization.LongSerializer
mp.messaging.outgoing.orders-sell.value.serializer = io.quarkus.kafka.client.serialization.ObjectMapperSerializer

mp.messaging.incoming.transactions.connector = smallrye-kafka
mp.messaging.incoming.transactions.topic = transactions
mp.messaging.incoming.transactions.value.deserializer = pl.piomin.samples.streams.order.model.deserializer.TransactionDeserializer

Đó là tất cả. Trình tạo đơn đặt hàng của chúng tôi đã sẵn sàng. Nếu bạn là order-serviceứng dụng Quarkus cũng sẽ chạy phiên bản Kafka (Redpanda). Nhưng trước tiên, hãy chuyển sang ứng dụng mẫu thứ hai - stock-service.

Sử dụng suối Kafka với Quarkus

Trong phần trước, chúng tôi đã gửi tin nhắn đến nhà môi giới Kafka. Do đó, chúng tôi đã sử dụng thư viện Quarkus tiêu chuẩn để tích hợp với Kafka dựa trên khung Nhắn tin phản ứng SmallRye. Ứng stock-servicedụng sử dụng thông báo dưới dạng luồng, vì vậy bây giờ chúng tôi sẽ sử dụng một mô-đun để tích hợp Kafka Streams.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-kafka-streams</artifactId>
</dependency>

Ứng dụng của chúng tôi cũng sử dụng cơ sở dữ liệu, lớp ORM và bao gồm một số mô-đun hữu ích khác.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

Trong bước đầu tiên, chúng ta sẽ hợp nhất cả hai luồng lệnh (mua và bán), chèn  Order vào cơ sở dữ liệu và in thông báo sự kiện. Bạn có thể hỏi - tại sao tôi sử dụng cơ sở dữ liệu và lớp ORM ở đây vì tôi có Kafka  KTable? Tôi cần các giao dịch có hỗ trợ khóa để điều phối trạng thái thực hiện đơn hàng (tham khảo mô tả trong phần giới thiệu - các đơn hàng được thực hiện đầy đủ và một phần). Tôi sẽ cung cấp cho bạn thêm chi tiết về nó trong các phần tiếp theo.

Để xử lý các luồng với Quarkus, chúng ta cần khai báo org.apache.kafka.streams.Topologybean. Nó chứa tất cả các định nghĩa KStreamKTable. Hãy bắt đầu chỉ với phần chịu trách nhiệm tạo và phát ra các giao dịch từ các đơn đặt hàng đến. Có hai KStreamđịnh nghĩa được tạo ra. Đầu tiên trong số họ chịu trách nhiệm hợp nhất hai luồng đơn hàng thành một luồng duy nhất và sau đó chèn luồng mới Ordervào cơ sở dữ liệu. Thứ hai trong số họ tạo và thực hiện các giao dịch bằng cách tham gia hai luồng bằng cách sử dụng productIdkhóa. Nhưng nhiều hơn về nó trong phần tiếp theo.

@Produces
public Topology buildTopology() {
   ObjectMapperSerde<Order> orderSerde = 
      new ObjectMapperSerde<>(Order.class);
   ObjectMapperSerde<Transaction> transactionSerde = 
      new ObjectMapperSerde<>(Transaction.class);

   StreamsBuilder builder = new StreamsBuilder();

   KStream<Long, Order> orders = builder.stream(
      ORDERS_SELL_TOPIC,
      Consumed.with(Serdes.Long(), orderSerde));

   builder.stream(ORDERS_BUY_TOPIC, 
         Consumed.with(Serdes.Long(), orderSerde))
      .merge(orders)
      .peek((k, v) -> {
         log.infof("New: %s", v);
         logic.add(v);
      });

   builder.stream(ORDERS_BUY_TOPIC, 
         Consumed.with(Serdes.Long(), orderSerde))
      .selectKey((k, v) -> v.getProductId())
      .join(orders.selectKey((k, v) -> v.getProductId()),
         this::execute,
         JoinWindows.of(Duration.ofSeconds(10)),
         StreamJoined.with(Serdes.Integer(), orderSerde, orderSerde))
      .filterNot((k, v) -> v == null)
      .map((k, v) -> new KeyValue<>(v.getId(), v))
      .peek((k, v) -> log.infof("Done -> %s", v))
      .to(TRANSACTIONS_TOPIC, Produced.with(Serdes.Long(), transactionSerde));

}

Để xử lý các luồng, chúng ta cần thêm các thuộc tính cấu hình. Một danh sách các chủ đề đầu vào là bắt buộc. Chúng tôi cũng có thể ghi đè một id ứng dụng mặc định và kích hoạt kiểm tra tình trạng Kafka.

quarkus.kafka-streams.application-id = stock
quarkus.kafka-streams.topics = orders.buy,orders.sell
quarkus.kafka.health.enabled = true

Hoạt động trên Kafka Streams

Giờ đây, chúng tôi có thể sử dụng một số thao tác nâng cao hơn trên Luồng Kafka thay vì chỉ hợp nhất hai luồng khác nhau. Trên thực tế, đó là một logic quan trọng trong ứng dụng của chúng tôi. Chúng ta cần kết hợp hai luồng thứ tự khác nhau thành một luồng duy nhất bằng cách sử dụng  productId làm khóa tham gia. Vì trình sản xuất đặt  orderId làm khóa thông báo, trước tiên chúng ta cần gọi  selectKey phương thức cho cả  luồng order.sell và  orders.buy luồng. Trong trường hợp của chúng tôi, tham gia các lệnh mua và bán liên quan đến cùng một sản phẩm chỉ là bước đầu tiên. Sau đó, chúng ta cần xác minh xem giá tối đa trong lệnh mua không lớn hơn giá tối thiểu trong lệnh bán hay không.

Bước tiếp theo là xác minh nếu cả hai điều này chưa được nhận ra trước đây, vì chúng cũng có thể được ghép nối với các đơn đặt hàng khác trong luồng. Nếu tất cả các điều kiện được đáp ứng, chúng tôi có thể tạo một giao dịch mới. Cuối cùng, chúng tôi có thể thay đổi khóa luồng từ productId thành  transactionId và gửi nó đến  transactions chủ đề dành riêng.

Mỗi khi tham gia thành công hai lệnh, chúng tôi đang cố gắng tạo một giao dịch. Phương execute(...)thức được gọi trong KStream joinphương thức. Thứ nhất, chúng tôi đang so sánh giá của cả hai đơn đặt hàng. Sau đó, chúng tôi xác minh trạng thái hiện thực của cả hai đơn đặt hàng bằng cách truy cập cơ sở dữ liệu H2. Nếu đơn đặt hàng vẫn chưa được thực hiện đầy đủ, chúng tôi có thể tạo giao dịch và cập nhật hồ sơ đơn đặt hàng trong cơ sở dữ liệu.

private Transaction execute(Order orderBuy, Order orderSell) {
   if (orderBuy.getAmount() >= orderSell.getAmount()) {
      int count = Math.min(orderBuy.getProductCount(), 
                           orderSell.getProductCount());
      boolean allowed = logic
         .performUpdate(orderBuy.getId(), orderSell.getId(), count);
      if (!allowed)
         return null;
      else
         return new Transaction(
            ++transactionId,
            orderBuy.getId(),
            orderSell.getId(),
            count,
            (orderBuy.getAmount() + orderSell.getAmount()) / 2,
            LocalDateTime.now(),
            "NEW"
      );
   } else {
            return null;
   }
}

Chúng ta hãy xem xét kỹ hơn  performUpdate() phương thức được gọi là bên trong  execute() phương thức. Nó bắt đầu một giao dịch và khóa cả hai  Order thực thể. Sau đó, nó xác minh từng trạng thái thực hiện đơn hàng và cập nhật nó với các giá trị hiện tại nếu có thể. Chỉ khi  performUpdate() phương thức kết thúc thành công, stock-serviceứng dụng mới tạo một giao dịch mới.

@ApplicationScoped
public class OrderLogic {

    @Inject
    Logger log;
    @Inject
    OrderRepository repository;

    @Transactional
    public Order add(Order order) {
        repository.persist(order);
        return order;
    }

    @Transactional
    public boolean performUpdate(Long buyOrderId, Long sellOrderId, int amount) {
        Order buyOrder = repository.findById(buyOrderId, 
           LockModeType.PESSIMISTIC_WRITE);
        Order sellOrder = repository.findById(sellOrderId, 
           LockModeType.PESSIMISTIC_WRITE);
        if (buyOrder == null || sellOrder == null)
            return false;
        int buyAvailableCount = 
           buyOrder.getProductCount() - buyOrder.getRealizedCount();
        int sellAvailableCount = 
           sellOrder.getProductCount() - sellOrder.getRealizedCount();
        if (buyAvailableCount >= amount && sellAvailableCount >= amount) {
            buyOrder.setRealizedCount(buyOrder.getRealizedCount() + amount);
            sellOrder.setRealizedCount(sellOrder.getRealizedCount() + amount);
            repository.persist(buyOrder);
            repository.persist(sellOrder);
            return true;
        } else {
            return false;
        }
    }
}

Tốt🙂Đó là tất cả những gì chúng ta cần làm trong phần đầu tiên của bài tập. Bây giờ chúng tôi có thể chạy cả hai ứng dụng mẫu của chúng tôi.

Chạy và quản lý ứng dụng Kafka Streams với Quarkus

Như tôi đã đề cập trước đây, trước tiên chúng ta cần bắt đầu order-service. Nó chạy một phiên bản Kafka mới và tạo tất cả các chủ đề bắt buộc. Ngay sau khi khởi động, nó đã sẵn sàng để gửi các đơn đặt hàng mới. Để chạy ứng dụng Quarkus cục bộ, chỉ cần vào thư mục order-servicevà thực hiện lệnh sau:

$ mvn quarkus:dev

Chỉ để xác minh, bạn có thể hiển thị danh sách đang chạy các vùng chứa Docker bằng docker pslệnh. Đây là kết quả của tôi:

Như bạn thấy phiên bản Redpanda đang chạy và nó có sẵn trên một cổng ngẫu nhiên 49724. Quarkus đã làm điều đó cho chúng tôi. Tuy nhiên, nếu bạn đã cài đặt Redpanda trên máy tính xách tay của mình, bạn hãy xem danh sách các chủ đề đã tạo với CLI của chúng rpk:

$ rpk topic list --brokers=127.0.0.1:49724

Sau đó, hãy chạy stock-service. Chuyển đến thư mục stock-servicevà chạy mvn quarkus:devlại một lần nữa. Sau khi khởi động, nó chỉ hoạt động. Cả hai ứng dụng đều chia sẻ cùng một phiên bản nhờ Dịch vụ Quarkus Dev. Bây giờ chúng ta hãy truy cập bảng điều khiển giao diện người dùng Quarkus Dev có sẵn tại http://localhost:8080/q/dev/. Tìm ô có tiêu đề “Apache Kafka Streams” .

Bạn có thể kiểm tra hình ảnh trực quan về cấu trúc liên kết Kafka Streams của chúng tôi. Tôi sẽ chia hình ảnh thành hai phần để hiển thị tốt hơn.

Sử dụng Kafka KTable với Quarkus

Chúng tôi đã hoàn thành việc triển khai logic chịu trách nhiệm tạo giao dịch từ các đơn đặt hàng đến. Trong bước tiếp theo, chúng tôi sẽ thực hiện các hoạt động phân tích trên luồng giao dịch. Mục tiêu chính của chúng tôi là tính toán tổng số giao dịch, tổng số sản phẩm đã bán / mua và tổng giá trị giao dịch ( price * productsCount) trên mỗi sản phẩm. Đây là lớp đối tượng được sử dụng trong tính toán.

@RegisterForReflection
public class TransactionTotal {
   private int count;
   private int amount;
   private int productCount;

   // GETTERS AND SETTERS
}

Vì  Transaction đối tượng không chứa thông tin về sản phẩm nên trước tiên chúng ta cần tham gia đơn hàng để truy cập. Sau đó, chúng tôi tạo ra một  KTable theo mỗi  productId nhóm và tổng hợp. Sau đó, chúng tôi có thể gọi một phương pháp tổng hợp cho phép chúng tôi thực hiện một số phép tính phức tạp hơn. Trong trường hợp cụ thể đó, chúng tôi đang tính toán số lượng tất cả các giao dịch đã thực hiện, khối lượng sản phẩm và tổng giá trị của chúng. Kết quả  KTable có thể được hiện thực hóa dưới dạng kho lưu trữ trạng thái. Nhờ đó, chúng tôi sẽ có thể truy vấn nó theo tên được xác định bởi TRANSACTIONS_PER_PRODUCT_SUMMARYbiến.

KeyValueBytesStoreSupplier storePerProductSupplier = Stores.persistentKeyValueStore(
   TRANSACTIONS_PER_PRODUCT_SUMMARY);

builder.stream(TRANSACTIONS_TOPIC, Consumed.with(Serdes.Long(), transactionSerde))
   .selectKey((k, v) -> v.getSellOrderId())
   .join(orders.selectKey((k, v) -> v.getId()),
      (t, o) -> new TransactionWithProduct(t, o.getProductId()),
      JoinWindows.of(Duration.ofSeconds(10)),
      StreamJoined.with(Serdes.Long(), transactionSerde, orderSerde))
   .groupBy((k, v) -> v.getProductId(), Grouped.with(Serdes.Integer(), transactionWithProductSerde))
   .aggregate(
      TransactionTotal::new,
      (k, v, a) -> {
         a.setCount(a.getCount() + 1);
         a.setProductCount(a.getAmount() + v.getTransaction().getAmount());
         a.setAmount(a.getProductCount() +
            (v.getTransaction().getAmount() * v.getTransaction().getPrice()));
         return a;
      },
      Materialized.<Integer, TransactionTotal> as(storePerProductSupplier)
         .withKeySerde(Serdes.Integer())
         .withValueSerde(transactionTotalSerde))
   .toStream()
   .peek((k, v) -> log.infof("Total per product(%d): %s", k, v))
   .to(TRANSACTIONS_PER_PRODUCT_AGGREGATED_TOPIC, 
      Produced.with(Serdes.Integer(), transactionTotalSerde));

Đây là lớp chịu trách nhiệm triển khai các truy vấn tương tác. Nó chích KafkaStreamsđậu. Sau đó, nó cố gắng lấy được lưu trữ liên tục dựa trên StockService.TRANSACTIONS_PER_PRODUCT_SUMMARYbiến. Kết quả là có một ReadOnlyKeyValueStorevới Integernhư một khóa và TransactionTotalmột giá trị. Chúng tôi có thể trả về một giá trị duy nhất liên quan đến productId( getTransactionsPerProductData) cụ thể hoặc chỉ trả về một danh sách với kết quả cho tất cả các sản phẩm có sẵn ( getAllTransactionsPerProductData).

@ApplicationScoped
public class InteractiveQueries {

   @Inject
   KafkaStreams streams;

   public TransactionTotal getTransactionsPerProductData(Integer productId) {
      return getTransactionsPerProductStore().get(productId);
   }

   public Map<Integer, TransactionTotal> getAllTransactionsPerProductData() {
      Map<Integer, TransactionTotal> m = new HashMap<>();
      KeyValueIterator<Integer, TransactionTotal> it = getTransactionsPerProductStore().all();
      while (it.hasNext()) {
         KeyValue<Integer, TransactionTotal> kv = it.next();
         m.put(kv.key, kv.value);
      }
      return m;
   }

   private ReadOnlyKeyValueStore<Integer, TransactionTotal> getTransactionsPerProductStore() {
      return streams.store(
         StoreQueryParameters
            .fromNameAndType(StockService.TRANSACTIONS_PER_PRODUCT_SUMMARY, QueryableStoreTypes.keyValueStore()));
   }

}

Cuối cùng, chúng ta có thể tạo một bộ điều khiển REST chịu trách nhiệm hiển thị dữ liệu được truy xuất bởi các truy vấn tương tác.

@ApplicationScoped
@Path("/transactions")
public class TransactionResource {

    @Inject
    InteractiveQueries interactiveQueries;

    @GET
    @Path("/products/{id}")
    public TransactionTotal getByProductId(@PathParam("id") Integer productId) {
        return interactiveQueries.getTransactionsPerProductData(productId);
    }

    @GET
    @Path("/products")
    public Map<Integer, TransactionTotal> getAllPerProductId() {
        return interactiveQueries.getAllTransactionsPerProductData();
    }

}

Bây giờ bạn có thể dễ dàng kiểm tra số liệu thống kê liên quan đến các giao dịch được tạo bởi stock-service. Bạn chỉ cần gọi các điểm cuối REST sau, ví dụ:

$ curl http://localhost:8080/transactions/products
$ curl http://localhost:8080/transactions/products/3
$ curl http://localhost:8080/transactions/products/5

Lời kết

Quarkus đơn giản hóa việc làm việc với Kafka Streams và các truy vấn tương tác. Nó cung cấp các cải tiến hữu ích cho các nhà phát triển như tự động khởi động Kafka trong chế độ phát triển và thử nghiệm hoặc trực quan hóa luồng Kafka trong bảng điều khiển giao diện người dùng dev. Bạn có thể dễ dàng so sánh phương pháp tiếp cận Quarkus với sự hỗ trợ của Spring Cloud Stream Kafka vì tôi đã triển khai cùng một logic cho cả hai khuôn khổ đó. Đây là kho lưu trữ GitHub với ví dụ về Spring Cloud Stream Kafka Streams.

Liên kết: https://piotrminkowski.com/2021/11/24/kafka-streams-with-quarkus/

#quarkus #java #kafka 

Cách Sử Dụng Suối Kafka Với Quarkus

Как использовать потоки Kafka с Quarkus

В этой статье вы узнаете, как использовать Kafka Streams с Quarkus. Мы собираемся использовать Quarkus вместо Spring Cloud. Если вы хотите разобраться, что такое потоковая платформа и чем она отличается от традиционного брокера сообщений, эта статья для вас. Кроме того, мы изучим полезные улучшения, связанные с Apache Kafka, предоставленные Quarkus.

Архитектура

В нашем случае есть два входящих потока событий. Оба они представляют входящие заказы. Эти заказы генерируются  order-service приложением. Он отправляет ордера на покупку в  orders.buy тему и ордера на продажу в  orders.sell тему. Затем  stock-service приложение получает и обрабатывает входящие события. На первом этапе необходимо изменить ключ каждого сообщения  orderId с  productId. Это потому, что он должен объединять заказы из разных тем, связанных с одним и тем же продуктом, для выполнения транзакций. Наконец, цена сделки представляет собой среднее значение цен продажи и покупки.

quarkus-kafka-потоки-арка

Мы создаем упрощенную версию платформы фондового рынка. Каждый заказ на покупку содержит максимальную цену, по которой клиент рассчитывает купить продукт. С другой стороны, каждый заказ на продажу содержит минимальную цену, по которой покупатель готов продать свой товар. Если цена ордера на продажу не превышает цену ордера на покупку определенного товара, мы совершаем транзакцию.

Каждый приказ действителен в течение 10 секунд. По истечении этого времени  stock-service приложение не будет обрабатывать такой заказ, так как он считается просроченным. Каждый заказ содержит ряд продуктов для транзакции. Например, мы можем продать 100 за 10 или купить 200 за 11. Следовательно, ордер может быть реализован полностью или частично. Приложение  stock-service пытается присоединить частично реализованные заказы к другим новым или частично реализованным заказам. Вы можете увидеть визуализацию этого процесса на картинке ниже.

quarkus-kafka-потоки-приложение

Запустите Apache Kafka локально

Прежде чем мы перейдем к реализации, нам нужно запустить локальный экземпляр Apache Kafka. Если вы не хотите устанавливать его на свой ноутбук, лучше всего запустить его с помощью  Redpanda . Redpanda — это потоковая платформа, совместимая с Kafka API. По сравнению с Kafka, его относительно легко запустить локально. Обычно вам нужно установить Redpanda на свой ноутбук, а затем создать кластер с помощью интерфейса командной строки. Но с Quarkus этого делать не нужно! Единственное требование — установить Docker. Благодаря расширению Quarkus Kafka и функции под названием Dev Services он автоматически запускает брокера Kafka в режиме разработки и при выполнении тестов. Более того, приложение настраивается автоматически.

Единственное, что вам нужно сделать, чтобы включить эту функцию, это НЕ указывать адрес Kafka в свойствах конфигурации. Dev Services использует Testcontainers для запуска Kafka, поэтому, если у вас есть Docker или любая другая среда, поддерживающая запуск Testcontainers, вы получаете контейнерный экземпляр Kafka из коробки. Еще одна важная вещь. Во-первых, запустите order-serviceприложение. Он автоматически создает все необходимые темы в Kafka. Затем запустите stock-serviceприложение. Он использует расширение Quarkus Kafka Streams и проверяет наличие необходимых тем. Давайте визуализируем это.

quarkus-kafka-streams-run

Отправляйте события в Kafka с помощью Quarkus

Существует несколько способов отправки событий в Kafka с помощью Quarkus. Поскольку нам нужно отправить пару ключ/значение, мы будем использовать io.smallrye.reactive.messaging.kafka.Recordдля этого объект. Quarkus может непрерывно генерировать и отправлять данные. Во фрагменте кода, показанном ниже, мы отправляем одно Orderсобытие каждые 500 мс. Каждый Orderсодержит случайный productId, priceи productCount.

@Outgoing("orders-buy")
public Multi<Record<Long, Order>> buyOrdersGenerator() {
   return Multi.createFrom().ticks().every(Duration.ofMillis(500))
      .map(order -> {
         Integer productId = random.nextInt(10) + 1;
         int price = prices.get(productId) + random.nextInt(200);
         Order o = new Order(
             incrementOrderId(),
             random.nextInt(1000) + 1,
             productId,
             100 * (random.nextInt(5) + 1),
             LocalDateTime.now(),
             OrderType.BUY,
             price);
         log.infof("Sent: %s", o);
         return Record.of(o.getId(), o);
   });
}

@Outgoing("orders-sell")
public Multi<Record<Long, Order>> sellOrdersGenerator() {
   return Multi.createFrom().ticks().every(Duration.ofMillis(500))
      .map(order -> {
         Integer productId = random.nextInt(10) + 1;
         int price = prices.get(productId) + random.nextInt(200);
         Order o = new Order(
             incrementOrderId(),
             random.nextInt(1000) + 1,
             productId,
             100 * (random.nextInt(5) + 1),
             LocalDateTime.now(),
             OrderType.SELL,
             price);
         log.infof("Sent: %s", o);
         return Record.of(o.getId(), o);
   });
}

Мы также определим один @Incomingканал для получения транзакций, созданных stock-service. Благодаря этому Quarkus автоматически создаст тему transactions, используемую Quarkus Kafka Streams, в файлах stock-service. Честно говоря, мне не удалось заставить расширение Quarkus Kafka Streams создавать тему автоматически. Кажется, нам нужно использовать для этого расширение SmallRye Reactive Messaging.

@Incoming("transactions")
public void transactions(Transaction transaction) {
   log.infof("New: %s", transaction);
}

Конечно, нам нужно включить зависимость SmallRye Reactive Messaging от Maven pom.xml.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-reactive-messaging-kafka</artifactId>
</dependency>

Наконец, давайте предоставим параметры конфигурации. У нас есть две исходящие темы и одна входящая тема. Мы можем установить их имена. В противном случае Quarkus использует то же имя, что и имя канала. Названия наших тем orders.buy, order.sellи transactions.

mp.messaging.outgoing.orders-buy.connector = smallrye-kafka
mp.messaging.outgoing.orders-buy.topic = orders.buy
mp.messaging.outgoing.orders-buy.key.serializer = org.apache.kafka.common.serialization.LongSerializer
mp.messaging.outgoing.orders-buy.value.serializer = io.quarkus.kafka.client.serialization.ObjectMapperSerializer

mp.messaging.outgoing.orders-sell.connector = smallrye-kafka
mp.messaging.outgoing.orders-sell.topic = orders.sell
mp.messaging.outgoing.orders-sell.key.serializer = org.apache.kafka.common.serialization.LongSerializer
mp.messaging.outgoing.orders-sell.value.serializer = io.quarkus.kafka.client.serialization.ObjectMapperSerializer

mp.messaging.incoming.transactions.connector = smallrye-kafka
mp.messaging.incoming.transactions.topic = transactions
mp.messaging.incoming.transactions.value.deserializer = pl.piomin.samples.streams.order.model.deserializer.TransactionDeserializer

Это все. Наш генератор заказов готов. Если вы используете order-serviceприложение Quarkus, оно также будет запускать экземпляр Kafka (Redpanda). Но сначала переключимся на второй пример приложения — stock-service.

Использование потоков Kafka с помощью Quarkus

В предыдущем разделе мы отправляли сообщения брокеру Kafka. Поэтому мы использовали стандартную библиотеку Quarkus для интеграции с Kafka на основе фреймворка SmallRye Reactive Messaging. Приложение stock-serviceпотребляет сообщения в виде потоков, поэтому сейчас мы воспользуемся модулем для интеграции с Kafka Streams.

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-kafka-streams</artifactId>
</dependency>

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

<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-smallrye-health</artifactId>
</dependency>

На первом этапе мы собираемся объединить оба потока ордеров (покупка и продажа), вставить их  Order в базу данных и распечатать сообщение о событии. Вы можете спросить — почему я использую здесь базу данных и слой ORM, если у меня есть Kafka  KTable? Ну а транзакции с поддержкой блокировки мне нужны для того, чтобы согласовывать статус реализации заказа (см. описание во введении — полностью и частично реализованные заказы). Я дам вам более подробную информацию об этом в следующих разделах.

Чтобы обрабатывать потоки с помощью Quarkus, нам нужно объявить org.apache.kafka.streams.Topologybean-компонент. Он содержит все определения KStreamи KTable. Начнем как раз с части, отвечающей за создание и эмитирование транзакций из входящих ордеров. Созданы два KStreamопределения. Первый из них отвечает за объединение двух потоков заказов в один и последующую вставку нового Orderв базу данных. Второй из них создает и выполняет транзакции, объединяя два потока по productIdключу. Но об этом в следующем разделе.

@Produces
public Topology buildTopology() {
   ObjectMapperSerde<Order> orderSerde = 
      new ObjectMapperSerde<>(Order.class);
   ObjectMapperSerde<Transaction> transactionSerde = 
      new ObjectMapperSerde<>(Transaction.class);

   StreamsBuilder builder = new StreamsBuilder();

   KStream<Long, Order> orders = builder.stream(
      ORDERS_SELL_TOPIC,
      Consumed.with(Serdes.Long(), orderSerde));

   builder.stream(ORDERS_BUY_TOPIC, 
         Consumed.with(Serdes.Long(), orderSerde))
      .merge(orders)
      .peek((k, v) -> {
         log.infof("New: %s", v);
         logic.add(v);
      });

   builder.stream(ORDERS_BUY_TOPIC, 
         Consumed.with(Serdes.Long(), orderSerde))
      .selectKey((k, v) -> v.getProductId())
      .join(orders.selectKey((k, v) -> v.getProductId()),
         this::execute,
         JoinWindows.of(Duration.ofSeconds(10)),
         StreamJoined.with(Serdes.Integer(), orderSerde, orderSerde))
      .filterNot((k, v) -> v == null)
      .map((k, v) -> new KeyValue<>(v.getId(), v))
      .peek((k, v) -> log.infof("Done -> %s", v))
      .to(TRANSACTIONS_TOPIC, Produced.with(Serdes.Long(), transactionSerde));

}

Для обработки потоков нам нужно добавить свойства конфигурации. Требуется список вводных тем. Мы также можем переопределить идентификатор приложения по умолчанию и включить проверку работоспособности Kafka.

quarkus.kafka-streams.application-id = stock
quarkus.kafka-streams.topics = orders.buy,orders.sell
quarkus.kafka.health.enabled = true

Операции над потоками Kafka

Теперь мы можем использовать более сложные операции с потоками Kafka, чем просто объединение двух разных потоков. На самом деле, это ключевая логика в нашем приложении. Нам нужно объединить два разных потока заказов в один, используя в  productId качестве ключа соединения. Поскольку производитель устанавливает  orderId в качестве ключа сообщения, нам сначала нужно вызвать  selectKey метод для обоих  order.sell и  orders.buy потоков. В нашем случае объединение заказов на покупку и продажу, связанных с одним и тем же продуктом, — это только первый шаг. Затем нам нужно проверить, не превышает ли максимальная цена в ордере на покупку минимальную цену в ордере на продажу.

Следующим шагом является проверка того, не были ли реализованы обе эти функции ранее, поскольку они также могут быть связаны с другими ордерами в потоке. Если все условия соблюдены, мы можем создать новую транзакцию. Наконец, мы можем изменить ключ потока с productId на  transactionId и отправить его в специальную  transactions тему.

Каждый раз, когда мы успешно объединяем два заказа, мы пытаемся создать транзакцию. Метод execute(...)вызывается внутри KStream joinметода. Во-первых, мы сравниваем цены обоих заказов. Затем мы проверяем статус реализации обоих заказов, обращаясь к базе данных H2. Если заказы еще не полностью реализованы, мы можем создать транзакцию и обновить записи заказов в базе данных.

private Transaction execute(Order orderBuy, Order orderSell) {
   if (orderBuy.getAmount() >= orderSell.getAmount()) {
      int count = Math.min(orderBuy.getProductCount(), 
                           orderSell.getProductCount());
      boolean allowed = logic
         .performUpdate(orderBuy.getId(), orderSell.getId(), count);
      if (!allowed)
         return null;
      else
         return new Transaction(
            ++transactionId,
            orderBuy.getId(),
            orderSell.getId(),
            count,
            (orderBuy.getAmount() + orderSell.getAmount()) / 2,
            LocalDateTime.now(),
            "NEW"
      );
   } else {
            return null;
   }
}

Давайте подробнее рассмотрим  performUpdate() метод, вызываемый внутри  execute() метода. Он инициирует транзакцию и блокирует обе  Order сущности. Затем он проверяет статус реализации каждого заказа и обновляет его текущими значениями, если это возможно. Только в случае  performUpdate() успешного завершения метода stock-serviceприложение создает новую транзакцию.

@ApplicationScoped
public class OrderLogic {

    @Inject
    Logger log;
    @Inject
    OrderRepository repository;

    @Transactional
    public Order add(Order order) {
        repository.persist(order);
        return order;
    }

    @Transactional
    public boolean performUpdate(Long buyOrderId, Long sellOrderId, int amount) {
        Order buyOrder = repository.findById(buyOrderId, 
           LockModeType.PESSIMISTIC_WRITE);
        Order sellOrder = repository.findById(sellOrderId, 
           LockModeType.PESSIMISTIC_WRITE);
        if (buyOrder == null || sellOrder == null)
            return false;
        int buyAvailableCount = 
           buyOrder.getProductCount() - buyOrder.getRealizedCount();
        int sellAvailableCount = 
           sellOrder.getProductCount() - sellOrder.getRealizedCount();
        if (buyAvailableCount >= amount && sellAvailableCount >= amount) {
            buyOrder.setRealizedCount(buyOrder.getRealizedCount() + amount);
            sellOrder.setRealizedCount(sellOrder.getRealizedCount() + amount);
            repository.persist(buyOrder);
            repository.persist(sellOrder);
            return true;
        } else {
            return false;
        }
    }
}

Nice🙂Это все, что нам нужно сделать в первой части нашего упражнения. Теперь мы можем запустить оба наших примера приложений.

Запускайте приложение Kafka Streams и управляйте им с помощью Quarkus

Как я уже упоминал ранее, нам сначала нужно запустить файл order-service. Он запускает новый экземпляр Kafka и создает все необходимые темы. Сразу после запуска он готов к отправке новых заказов. Чтобы запустить приложение Quarkus локально, просто перейдите в order-serviceкаталог и выполните следующую команду:

$ mvn quarkus:dev

Просто для проверки вы можете отобразить список запущенных контейнеров Docker с помощью docker psкоманды. Вот мой результат:

Как вы видите, экземпляр Redpanda запущен и доступен на произвольном порту 49724. Quarkus сделал это за нас. Однако, если на вашем ноутбуке установлена ​​Redpanda, вы можете проверить список созданных тем с их CLI rpk:

$ rpk topic list --brokers=127.0.0.1:49724

Тогда запустим stock-service. Перейдите в stock-serviceкаталог и запустите mvn quarkus:devеще раз. После запуска он просто работает. Оба приложения используют один и тот же экземпляр благодаря Quarkus Dev Services. Теперь давайте получим доступ к консоли пользовательского интерфейса Quarkus Dev, доступной по адресу http://localhost:8080/q/dev/. Найдите плитку с заголовком «Apache Kafka Streams» .

Вы можете проверить визуализацию нашей топологии Kafka Streams. Я разделю изображение на две части для лучшей наглядности.

Используйте Kafka KTable с Quarkus

Мы уже закончили реализацию логики, отвечающей за создание транзакций из поступающих заказов. На следующем шаге мы собираемся выполнить аналитические операции над потоком транзакций. Наша основная цель — рассчитать общее количество транзакций, общее количество проданных/купленных продуктов и общую стоимость транзакций ( price * productsCount) по каждому продукту. Вот класс объекта, используемый в вычислениях.

@RegisterForReflection
public class TransactionTotal {
   private int count;
   private int amount;
   private int productCount;

   // GETTERS AND SETTERS
}

Поскольку  Transaction объект не содержит информации о продукте, нам сначала нужно присоединиться к заказу, чтобы получить к нему доступ. Затем мы производим  KTable по  productId группировке и агрегации. После этого мы можем вызвать агрегатный метод, который позволит нам выполнить более сложные вычисления. В этом конкретном случае мы рассчитываем количество всех выполненных транзакций, их объем продуктов и общую стоимость. Результат  KTable может быть материализован как хранилище состояний. Благодаря этому мы сможем запрашивать его по имени, определенному TRANSACTIONS_PER_PRODUCT_SUMMARYпеременной.

KeyValueBytesStoreSupplier storePerProductSupplier = Stores.persistentKeyValueStore(
   TRANSACTIONS_PER_PRODUCT_SUMMARY);

builder.stream(TRANSACTIONS_TOPIC, Consumed.with(Serdes.Long(), transactionSerde))
   .selectKey((k, v) -> v.getSellOrderId())
   .join(orders.selectKey((k, v) -> v.getId()),
      (t, o) -> new TransactionWithProduct(t, o.getProductId()),
      JoinWindows.of(Duration.ofSeconds(10)),
      StreamJoined.with(Serdes.Long(), transactionSerde, orderSerde))
   .groupBy((k, v) -> v.getProductId(), Grouped.with(Serdes.Integer(), transactionWithProductSerde))
   .aggregate(
      TransactionTotal::new,
      (k, v, a) -> {
         a.setCount(a.getCount() + 1);
         a.setProductCount(a.getAmount() + v.getTransaction().getAmount());
         a.setAmount(a.getProductCount() +
            (v.getTransaction().getAmount() * v.getTransaction().getPrice()));
         return a;
      },
      Materialized.<Integer, TransactionTotal> as(storePerProductSupplier)
         .withKeySerde(Serdes.Integer())
         .withValueSerde(transactionTotalSerde))
   .toStream()
   .peek((k, v) -> log.infof("Total per product(%d): %s", k, v))
   .to(TRANSACTIONS_PER_PRODUCT_AGGREGATED_TOPIC, 
      Produced.with(Serdes.Integer(), transactionTotalSerde));

Вот класс, отвечающий за реализацию интерактивных запросов. Он вводит KafkaStreamsбоб. Затем он пытается получить постоянное хранилище на основе StockService.TRANSACTIONS_PER_PRODUCT_SUMMARYпеременной. В результате есть ReadOnlyKeyValueStorewith Integerи в качестве ключа, и TransactionTotalв качестве значения. Мы можем вернуть одно значение, связанное с конкретным productId( getTransactionsPerProductData), или просто вернуть список с результатами для всех доступных продуктов ( getAllTransactionsPerProductData).

@ApplicationScoped
public class InteractiveQueries {

   @Inject
   KafkaStreams streams;

   public TransactionTotal getTransactionsPerProductData(Integer productId) {
      return getTransactionsPerProductStore().get(productId);
   }

   public Map<Integer, TransactionTotal> getAllTransactionsPerProductData() {
      Map<Integer, TransactionTotal> m = new HashMap<>();
      KeyValueIterator<Integer, TransactionTotal> it = getTransactionsPerProductStore().all();
      while (it.hasNext()) {
         KeyValue<Integer, TransactionTotal> kv = it.next();
         m.put(kv.key, kv.value);
      }
      return m;
   }

   private ReadOnlyKeyValueStore<Integer, TransactionTotal> getTransactionsPerProductStore() {
      return streams.store(
         StoreQueryParameters
            .fromNameAndType(StockService.TRANSACTIONS_PER_PRODUCT_SUMMARY, QueryableStoreTypes.keyValueStore()));
   }

}

Наконец, мы можем создать контроллер REST, отвечающий за предоставление данных, полученных интерактивными запросами.

@ApplicationScoped
@Path("/transactions")
public class TransactionResource {

    @Inject
    InteractiveQueries interactiveQueries;

    @GET
    @Path("/products/{id}")
    public TransactionTotal getByProductId(@PathParam("id") Integer productId) {
        return interactiveQueries.getTransactionsPerProductData(productId);
    }

    @GET
    @Path("/products")
    public Map<Integer, TransactionTotal> getAllPerProductId() {
        return interactiveQueries.getAllTransactionsPerProductData();
    }

}

Теперь вы можете легко проверить статистику, связанную с транзакциями, созданными stock-service. Вам просто нужно вызвать следующие конечные точки REST, например:

$ curl http://localhost:8080/transactions/products
$ curl http://localhost:8080/transactions/products/3
$ curl http://localhost:8080/transactions/products/5

Последние мысли

Quarkus упрощает работу с Kafka Streams и интерактивными запросами. Он предоставляет полезные улучшения для разработчиков, такие как автоматический запуск Kafka в режимах разработки и тестирования или визуализацию потоков Kafka в консоли пользовательского интерфейса разработчика. Вы можете легко сравнить подход Quarkus с поддержкой Spring Cloud Stream Kafka, поскольку я реализовал одинаковую логику для обеих этих платформ. Вот репозиторий GitHub с примером Spring Cloud Stream Kafka Streams.

Ссылка: https://piotrminkowski.com/2021/11/24/kafka-streams-with-quarkus/

#quarkus #java #kafka 

Как использовать потоки Kafka с Quarkus

Quarkus Vs Springboot: Differences and Similarities Between Frameworks

quarkus-vs-springboot

A demo project that demonstrates the differences and similarities between the frameworks

Features:

  • Controller
    • REST interface to read all orders
    • REST interface to post a new order
    • New orders are send using Kafka to the order service
    • Existing orders are gotten trough HTTP from the order service
  • Service
    • Postgres database
    • Kafka consumer for new orders
    • REST interface for all orders (use paging)

demo

There is an action/sh script in /scripts. Use this to perform the following steps:

First for spring-boot:

  • ./action.sh build-image spring-boot order-controller
  • ./action.sh start-pods spring-boot order-controller 
    This will barely be able to start the pods in time and by doing so use a lot of resources.
  • ./action.sh replicas spring-boot order-controller 30 
    This will take forever with a lot of CrashLoopBackOff. Better do this in small increments of 4 replicas a time.
  • ./action.sh drain 
    This will drain one of the nodes and move all pods to another node at once. See the load increase and wait forever for this to have all pods running again.
  • ./action.sh uncordon
  • ./action.sh replicas spring-boot order-controller 1

Second for quarkus:

  • ./action.sh build-image quarkus order-controller
  • ./action.sh start-pods quarkus order-controller 
    This will quickly start the pods and use little resources.
  • ./action.sh replicas quarkus order-controller 30 
    There might be some restarts but it reaches the 30 pods quite quickly.
  • ./action.sh drain 
    This will drain one of the nodes and move all pods to another node at once. See how quickly this finishes and moves all the drained pods to the other node.
  • ./action.sh uncordon
  • ./action.sh replicas quarkus order-controller 1

Download Details:
Author: gupbeheer
Source Code: https://github.com/gupbeheer/quarkus-vs-springboot
License:

#spring #springboot #java #quarkus 

Quarkus Vs Springboot: Differences and Similarities Between Frameworks
Edward Jackson

Edward Jackson

1644909592

Coding in Java with Joy

Coding in Java with Joy

You can deploy your applications to Kubernetes; that's great! You can have pipelines and a breadth of tools to do analysis, etc. But what about how you develop and what you develop, and finally, how you interact with applications deployed in Kubernetes while in the development inner loop. How can that be done easier, simpler, faster? Java has been there for a long time, and all of us have ways to develop applications and tools that we all love. So how does Quarkus do any of this differently?

Live coding, adding functionality, extensions without restarting, spinning up dev services e.g., databases, making coding and testing easier and continuous, enhancing the inner loop, etc. In this talk, I will create an application from scratch with Quarkus, add databases to it, add a front-end, stream etc. And finally, work with it while it's deployed in Kubernetes using my dev machine and effectively live code remotely. Join this talk to learn about the Java developers journey to developer Joy!

#java #programming #developer #kubernetes #quarkus

Coding in Java with Joy

Build Secure REST APIs and GraalVM Native Images with Java Frameworks

Build Native Java Apps with Micronaut, Quarkus, and Spring Boot

Learn how to create secure REST APIs and GraalVM native images with popular Java frameworks: Micronaut, Quarkus, and Spring Boot.

Table of Contents:

00:00 - Hello Java developers!
01:17 - Install prerequisites
01:50 - Install a JDK with GraalVM
02:29 - Generate an OAuth 2.0 Access Token
05:16 - Make a Micronaut Java API
07:08 - Run and Test Your Micronaut API with HTTPie
07:43 - Build a Native Micronaut App
09:08 - Create a Quarkus Java API
11:08 - Run and Test Your Quarkus API with HTTPie
11:35 - Build a Native Quarkus App
12:34 - Start a Spring Boot Java API
14:16 - Run and Test Your Spring Boot API with HTTPie
14:53 - Build a Native Spring Boot App
16:12 - Startup Time Comparison
18:09 - Memory Usage Comparison
20:03 - Try it yourself
20:56 - Thanks for watching!

Blog post: https://developer.okta.com/blog/2021/06/18/native-java-framework-comparison 
GitHub repo: https://github.com/oktadev/native-java-examples 
Demo script: https://github.com/oktadev/native-java-examples/blob/main/demo.adoc 

#graalvm #java #rest #api #micronaut #quarkus #springboot

Build Secure REST APIs and GraalVM Native Images with Java Frameworks

How Quarkus Works with New Features in Java 17

If you'd like to experiment to see how Quarkus works with the new language features that have rolled out since Java 11, this article offers a technical preview to help you get started.

  1. Set up your development environment
  2. Bootstrap the Quarkus project
  3. Use sealed classes for API models
  4. Control API response using yield in switch expressions
  5. Using records when implementing fault tolerance
  6. Generate container images using Jib

#java #quarkus 

How Quarkus Works with New Features in Java 17
Mireille  Von

Mireille Von

1638374400

Presentation on What A Quarkus Is and How to Get Started with Quarkus.

This video covers what is quarkus and how to get started with quarkus.

#quarkus 

Presentation on What A Quarkus Is and How to Get Started with Quarkus.