0% ont trouvé ce document utile (0 vote)
474 vues34 pages

Quarkus JPA PostgreSQL

Ce tutoriel Quarkus-JPA-PostgreSQL met en œuvre : une API Rest partielle (GET) avec JAX-RS et Quarkus sur une source de données JPA ; des tests unitaires ; des tests d'intégration au niveau API (http) avec un PostGreSQL lancé par un plugin maven Docker ; une distribution native, compilée avec GraalVM et une image docker de l'application compilée.

Transféré par

MAFWANA
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
474 vues34 pages

Quarkus JPA PostgreSQL

Ce tutoriel Quarkus-JPA-PostgreSQL met en œuvre : une API Rest partielle (GET) avec JAX-RS et Quarkus sur une source de données JPA ; des tests unitaires ; des tests d'intégration au niveau API (http) avec un PostGreSQL lancé par un plugin maven Docker ; une distribution native, compilée avec GraalVM et une image docker de l'application compilée.

Transféré par

MAFWANA
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Tutoriel pour réduire l'empreinte

serveur d'une API REST en JAVA en la


compilant en code natif avec Quarkus

Par François-Xavier Robin

Date de publication : 13 mai 2020

Ce tutoriel Quarkus-JPA-PostgreSQL met en œuvre :

• une API Rest partielle (GET) avec JAX-RS et Quarkus sur une source de données
JPA ;
• des tests unitaires ;
• des tests d'intégration au niveau API (http) avec un PostGreSQL lancé par un plugin
maven Docker ;
• une distribution native, compilée avec GraalVM et une image docker de l'application
compilée.

Réalisé sous Linux Mint 19 mais devrait


convenir à de nombreuses distributions, voire
à Windows.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum
Commentez.
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

I - Choix techniques.....................................................................................................................................................3
II - Qu'est-ce que Quarkus.......................................................................................................................................... 3
III - Structure globale du projet................................................................................................................................... 3
IV - Maven et son pom.xml......................................................................................................................................... 5
V - Les plugins de build.............................................................................................................................................. 7
VI - Un simple /ping................................................................................................................................................... 10
VII - Compilation en binaire avec GraalVM...............................................................................................................13
VII-A - Installation de SDKMAN........................................................................................................................... 13
VII-B - Installation de GraalVM............................................................................................................................ 13
VII-C - Compilation en code natif........................................................................................................................ 14
VIII - Configuration de l'accès aux données............................................................................................................. 15
VIII-A - Lancement de PostgreSQL via Docker...................................................................................................15
VIII-B - Paramétrage de l'application................................................................................................................... 16
IX - Des entités à rendre persistantes...................................................................................................................... 17
IX-A - VideoGame et Genre.................................................................................................................................17
IX-B - Producers CDI et annotation..................................................................................................................... 18
IX-C - Le repository CRUD avec Spring Data JPA............................................................................................. 19
X - Le endpoint REST JAX-RS................................................................................................................................. 19
XI - Convertisseur générique pour les valeurs de l'enum......................................................................................... 21
XII - Tests...................................................................................................................................................................26
XII-A - Tests unitaires...........................................................................................................................................26
XII-B - Tests d'intégration..................................................................................................................................... 26
XIII - Version native et image Docker....................................................................................................................... 30
XIV - Health Check et Metrics...................................................................................................................................32
XV - Conclusions....................................................................................................................................................... 34
XVI - Remerciements.................................................................................................................................................34

-2-
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

I - Choix techniques

L'objectif de cet article est de faire tourner une API REST avec Quarkus fonctionnant avec :

• JAX-RS 2 : Avec RESTEasy et Jackson pour la sérialisation JSON ;


• CDI 2 : avec l'implémentation interne partielle de QUARKUS ;
• JPA 2 : avec Hibernate ;
• Bean Validation : avec Hibernate Validator ;
• Health Check et Metrics : avec SmallRye Health et SmallRye Metrics.

On équipera le projet de diverses bibliothèques pour accélérer le développement :

• Spring Data JPA : pour ses Repository CRUD JPA ;


• Lombok : pour réduire le boiler plate (> voir mon article sur Lombok) ;
• Open API avec Swagger 2 (mais ce n'est pas l'objet de ce tutoriel).

Nativement, Quarkus est fourni avec Google Guava, ce qui servira dans le cadre de ce tutoriel.

Le projet au complet est disponible sur GitHub.

II - Qu'est-ce que Quarkus

Sur la page d'accueil de Quarkus, on peut lire :

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.

En substance, c'est un framework constitué des meilleurs standards et bibliothèques Java pour réaliser des
applications pour le cloud en mode REST.

Ses concurrents directs sont les fameux :

• Micronaut ;
• Thorntail ;
• SpringBoot (et toute la plateforme Spring) ;
• dans une moindre mesure, Payara-micro.

III - Structure globale du projet

Avant de commencer à entrer dans le détail des divers éléments, voici la structure du projet Maven :

[quarkus-tuto]
├── src
│ ├── main
│ │ ├── docker
│ │ │ ├── Dockerfile.jvm
│ │ │ └── Dockerfile.native
│ │ ├── java
│ │ │ └── fr
│ │ │ └── fxjavadevblog
│ │ │ └── qjg
│ │ │ ├── genre
│ │ │ │ ├── Genre.java
│ │ │ │ ├── GenreConverterProvider.java
│ │ │ │ └── GenreResource.java

-3-
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

│ │ │ ├── health
│ │ │ │ └── SimpleHealthCheck.java
│ │ │ ├── ping
│ │ │ │ └── PingService.java
│ │ │ ├── utils
│ │ │ │ ├── GenericEnumConverter.java
│ │ │ │ ├── InjectUUID.java
│ │ │ │ └── UUIDProducer.java
│ │ │ ├── videogame
│ │ │ │ ├── VideoGame.java
│ │ │ │ ├── VideoGameFactory.java
│ │ │ │ ├── VideoGameRepository.java
│ │ │ │ └── VideoGameResource.java
│ │ │ └── ApiDefinition.java
│ │ └── resources
│ │ ├── application.properties
│ │ └── import.sql
│ ├── test
│ │ ├── java
│ │ │ └── fr
│ │ │ └── fxjavadevblog
│ │ │ └── qjg
│ │ │ ├── global
│ │ │ │ └── TestingGroups.java
│ │ │ ├── ping
│ │ │ │ └── PingTest.java
│ │ │ └── utils
│ │ │ ├── CDITest.java
│ │ │ ├── DummyTest.java
│ │ │ └── GenericEnumConverterTest.java
│ │ └── resources
│ │ └── application.properties
│ └── test-integration
│ ├── java
│ │ └── fr
│ │ └── fxjavadevblog
│ │ └── qjg
│ │ ├── utils
│ │ │ └── IT_DummyTest.java
│ │ └── videogame
│ │ └── IT_VideoGameResource.java
│ └── resources
│ └── application.properties
├── target
│ ├── classes
│ │ ├── application.properties
│ │ └── import.sql
│ └── test-classes
│ └── application.properties
├── .dockerignore
├── README.md
└── pom.xml

La structure du projet se décompose ainsi :

• src/main : contient les sources Java main/java et les ressources pour Quarkus main\resources :
application.properties et import.sql ;
• src/test : contient les tests unitaires test/java et les ressources pour les tests unitaires sans base de données
PostgreSQL test\resources ;
• src/test-integration : contient les tests d'intégration test-integration/java et les ressources pour les tests
d’intégration avec PostgreSQL test-integration\resources ;
• src/main/docker : contient les Dockerfile nécessaires à la génération de l'image conteneurisée de
l'application.

La partie JAVA se décompose en cinq packages :

fxjavadevblog
└── qjg

-4-
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

├── genre
│ ├── Genre.java : enum qui contient tous les genres de jeux vidéo
│ ├── GenreConverterProvider.java : fournisseur de conversion de Genre pour les
paramètres JAX-RS
│ └── GenreResource.java : point d’accès REST via JAX-RS aux genres de jeux
vidéo
├── health
│ └── SimpleHealthCheck.java : retour simple de Health Check (optionnel)
├── ping
│ └── PingService.java : pour vérifier que JAX-RS est bien opérationnel
├── utils
│ ├── GenericEnumConverter.java : convertisseur générique d’enum en Json
│ ├── InjectUUID.java : annotation pour injecter des UUID sous forme de
String
│ └── UUIDProducer.java : générateur de UUID
├── videogame
│ ├── VideoGame.java : classe métier, persistante via JPA (Hibernate)
│ ├── VideoGameFactory.java : Factory de jeux video via CDI pour bénéficier de
@InjectUUID en mode programmatique
│ ├── VideoGameRepository.java : un repository CRUD JPA généré par Spring Data JPA
│ └── VideoGameResource.java : le point d’accès REST via JAX-RS aux jeux vidéo
└── ApiDefinition.java : pour les informations de l’API via Swagger

La partie tests unitaires est constituée des éléments suivants :

test
├── java
│ └── fr
│ └── fxjavadevblog
│ └── qjg
│ ├── global
│ │ └── TestingGroups.java : définitions de constantes pour les groupes de
tests JUnit 5
│ ├── ping
│ │ └── PingTest.java : Vérifie que le “ping” fonctionne
│ └── utils
│ ├── CDITest.java : permet de vérifier que CDI est
opérationnel
│ ├── DummyTest.java : un test vide
│ └── GenericEnumConverterTest.java : vérification de la conversion générique
d’enum
└── resources
└── application.properties : fichier de paramétrage de Quarkus spécifique pour
les tests unitaires

DummyTest.java : un test vide afin de vérifier que les tests unitaires s'exécutent correctement
(un métatest, lol)

IV - Maven et son pom.xml

D'abord il nous faut quelques paramétrages classiques Maven :

1. <project xmlns="http://maven.apache.org/POM/4.0.0"
2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/
maven-4.0.0.xsd">
4. <modelVersion>4.0.0</modelVersion>
5. <groupId>fr.fxjavadevblog</groupId>
6. <artifactId>quarkus-tuto</artifactId>
7. <version>0.0.1-SNAPSHOT</version>
8. <name>Quarkus-JPA-PostgreSQL</name>
9. <properties>
10. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
11. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
12. <maven.compiler.target>1.8</maven.compiler.target>

-5-
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

13. <maven.compiler.source>1.8</maven.compiler.source>
14. <lombok.version>1.18.12</lombok.version>
15. <quarkus-version>1.3.1.Final</quarkus-version>
16. <surefire-plugin.version>2.22.2</surefire-plugin.version>
17. </properties>
18. </project>

Pour ce tutoriel, on utilisera Java 8.

On ajoute les dépendances classiques :

1. <dependencies>
2. <dependency>
3. <groupId>org.projectlombok</groupId>
4. <artifactId>lombok</artifactId>
5. <version>${lombok.version}</version>
6. <scope>provided</scope>
7. </dependency>
8. </dependencies>

Pour utiliser Quarkus :

1. <dependencyManagement>
2. <dependencies>
3. <dependency>
4. <groupId>io.quarkus</groupId>
5. <artifactId>quarkus-bom</artifactId>
6. <version>${quarkus-version}</version>
7. <type>pom</type>
8. <scope>import</scope>
9. </dependency>
10. </dependencies>
11. </dependencyManagement>

puis :

1. <dependency>
2. <groupId>io.quarkus</groupId>
3. <artifactId>quarkus-spring-data-jpa</artifactId>
4. </dependency>
5. <dependency>
6. <groupId>io.quarkus</groupId>
7. <artifactId>quarkus-jackson</artifactId>
8. </dependency>
9. <dependency>
10. <groupId>io.quarkus</groupId>
11. <artifactId>quarkus-resteasy-jackson</artifactId>
12. </dependency>
13. <dependency>
14. <groupId>io.quarkus</groupId>
15. <artifactId>quarkus-jdbc-postgresql</artifactId>
16. </dependency>
17. <!-- OPEN API via Swagger 2 -->
18. <dependency>
19. <groupId>io.quarkus</groupId>
20. <artifactId>quarkus-smallrye-openapi</artifactId>
21. </dependency>
22. <!-- Health Check -->
23. <dependency>
24. <groupId>io.quarkus</groupId>
25. <artifactId>quarkus-smallrye-health</artifactId>
26. </dependency>
27. <!-- Metrics -->
28. <dependency>
29. <groupId>io.quarkus</groupId>
30. <artifactId>quarkus-smallrye-metrics</artifactId>

-6-
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

31. </dependency>
32. <!-- for testing -->
33. <dependency>
34. <groupId>io.quarkus</groupId>
35. <artifactId>quarkus-junit5</artifactId>
36. <scope>test</scope>
37. </dependency>
38. <!-- for testing -->
39. <dependency>
40. <groupId>io.rest-assured</groupId>
41. <artifactId>rest-assured</artifactId>
42. <scope>test</scope>
43. </dependency>

V - Les plugins de build

Attention, ils sont nombreux, mais ce n'est pas rare pour des projets Maven.

Il nous faut de quoi :

• générer tout ce qui est traité par Quarkus ;


• lancer les tests unitaires sans base de données ;
• démarrer notre base de données PostgreSQL avec Docker pendant les tests d'intégration JUnit 5. On est
ainsi à mi-chemin entre des tests unitaires et des tests d'intégration. Je préfère cette solution plutôt que de
mocker les tests. Cela nécessite évidemment que Docker soit installé sur l'environnement.

1. <build>
2. <plugins>
3. <plugin>
4. <artifactId>maven-compiler-plugin</artifactId>
5. <version>3.8.1</version>
6. </plugin>
7. <plugin>
8. <groupId>io.quarkus</groupId>
9. <artifactId>quarkus-maven-plugin</artifactId>
10. <version>${quarkus-version}</version>
11. <executions>
12. <execution>
13. <goals>
14. <goal>build</goal>
15. </goals>
16. </execution>
17. </executions>
18. </plugin>
19. <plugin>
20. <artifactId>maven-resources-plugin</artifactId>
21. <version>3.1.0</version>
22. <executions>
23. <execution>
24. <id>copy-resources</id>
25. <phase>pre-integration-test</phase>
26. <goals>
27. <goal>copy-resources</goal>
28. </goals>
29. <configuration>
30. <overwrite>true</overwrite>
31. <outputDirectory>${basedir}/target/test-classes</outputDirectory>
32. <resources>
33. <resource>
34. <directory>src/test-integration/resources</directory>
35. <filtering>true</filtering>
36. </resource>
37. </resources>
38. </configuration>
39. </execution>
40. </executions>
41. </plugin>

-7-
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

42. <!-- tests unitaires -->


43. <plugin>
44. <artifactId>maven-surefire-plugin</artifactId>
45. <version>${surefire-plugin.version}</version>
46. <configuration>
47. <excludes>
48. <exclude>**/IT_*.java</exclude>
49. </excludes>
50. <systemProperties>
51. <java.util.logging.manager>org.jboss.logmanager.LogManager</
java.util.logging.manager>
52. </systemProperties>
53. <skipTests>${skip.surefire.tests}</skipTests>
54. </configuration>
55. </plugin>
56. <!-- tests d'integration -->
57. <plugin>
58. <groupId>org.apache.maven.plugins</groupId>
59. <artifactId>maven-failsafe-plugin</artifactId>
60. <version>${surefire-plugin.version}</version>
61. <executions>
62. <execution>
63. <goals>
64. <goal>integration-test</goal>
65. <goal>verify</goal>
66. </goals>
67. </execution>
68. </executions>
69. </plugin>
70. <plugin>
71. <groupId>org.codehaus.mojo</groupId>
72. <artifactId>build-helper-maven-plugin</artifactId>
73. <version>3.1.0</version>
74. <executions>
75. <execution>
76. <id>add-integration-test-sources</id>
77. <phase>generate-test-sources</phase>
78. <goals>
79. <goal>add-test-source</goal>
80. </goals>
81. <configuration>
82. <sources>
83. <source>src/test-integration/java</source>
84. </sources>
85. </configuration>
86. </execution>
87. <execution>
88. <id>add-integration-test-resource</id>
89. <phase>generate-test-resources</phase>
90. <goals>
91. <goal>add-test-resource</goal>
92. </goals>
93. <configuration>
94. <resources>
95. <resource>
96. <directory>src/test-integration/resources</directory>
97. </resource>
98. </resources>
99. </configuration>
100. </execution>
101. </executions>
102. </plugin>
103. <plugin>
104. <groupId>io.fabric8</groupId>
105. <artifactId>docker-maven-plugin</artifactId>
106. <version>0.33.0</version>
107. <configuration>
108. <skip>${skip.integration.tests}</skip>
109. <images>
110. <image>
111. <name>postgres:12.2</name>
112. <alias>postgresql</alias>

-8-
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

113. <run>
114. <env>
115. <POSTGRES_USER>quarkus_tuto</POSTGRES_USER>
116. <POSTGRES_PASSWORD>quarkus_tuto</POSTGRES_PASSWORD>
117. <POSTGRES_DB>quarkus_tuto</POSTGRES_DB>
118. </env>
119. <ports>
120. <port>5432:5432</port>
121. </ports>
122. <log>
123. <prefix>PostgreSQL Server : </prefix>
124. <date>default</date>
125. <color>green</color>
126. </log>
127. <wait>
128. <tcp>
129. <mode>mapped</mode>
130. <ports>
131. <port>5432</port>
132. </ports>
133. </tcp>
134. <time>10000</time>
135. </wait>
136. </run>
137. </image>
138. </images>
139. </configuration>
140. <executions>
141. <execution>
142. <id>docker:start</id>
143. <phase>pre-integration-test</phase>
144. <goals>
145. <goal>stop</goal>
146. <goal>start</goal>
147. </goals>
148. </execution>
149. <execution>
150. <id>docker:stop</id>
151. <phase>post-integration-test</phase>
152. <goals>
153. <goal>stop</goal>
154. </goals>
155. </execution>
156. </executions>
157. </plugin>
158. </plugins>
159. </build>

Et enfin, pour atteindre le Graal du code Java compilé en binaire natif, il nous faut rajouter un petit profil Maven :

1. <profiles>
2. <profile>
3. <id>native</id>
4. <activation>
5. <property>
6. <name>native</name>
7. </property>
8. </activation>
9. <build>
10. <plugins>
11. <plugin>
12. <groupId>io.quarkus</groupId>
13. <artifactId>quarkus-maven-plugin</artifactId>
14. <version>${quarkus-plugin.version}</version>
15. <executions>
16. <execution>
17. <goals>
18. <goal>native-image</goal>
19. </goals>
20. <configuration>
21. <enableHttpUrlHandler>true</enableHttpUrlHandler>

-9-
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

22. </configuration>
23. </execution>
24. </executions>
25. </plugin>
26. <plugin>
27. <groupId>org.apache.maven.plugins</groupId>
28. <artifactId>maven-failsafe-plugin</artifactId>
29. <version>${surefire-plugin.version}</version>
30. <executions>
31. <execution>
32. <goals>
33. <goal>integration-test</goal>
34. <goal>verify</goal>
35. </goals>
36. <configuration>
37. <systemProperties>
38. <native.image.path>${project.build.directory}/
${project.build.finalName}-runner</native.image.path>
39. </systemProperties>
40. </configuration>
41. </execution>
42. </executions>
43. </plugin>
44. </plugins>
45. </build>
46. </profile>
47. </profiles>

Et voilà, le pom.xml est entièrement configuré.

Il est temps de coder quelques classes Quarkus dans votre IDE favori.

VI - Un simple /ping

Pour vérifier que tout va bien, on va faire un simple endpoint HTTP Rest avec JAX-RS qui va répondre à /api/ping/v1.

1. package fr.fxjavadevblog.qjg.ping;
2. import javax.ws.rs.GET;
3. import javax.ws.rs.Path;
4. import javax.ws.rs.Produces;
5. /**
6. * Simple JAX-RS endoint to check if the application is running.
7. *
8. * @author François-Xavier Robin
9. *
10. */
11. @Path("/api/ping")
12. public class PingService
13. {
14. @Path("/v1")
15. @GET
16. @Produces("text/plain")
17. public String ping()
18. {
19. return "pong";
20. }
21. }

On compile et on lance Quarkus en mode DEV.

1. $ mvn clean compile quarkus:dev


2. ...
3. ... ( build maven ...)
4. ...
5. Listening for transport dt_socket at address: 5005
6. SLF4J: Class path contains multiple SLF4J bindings.

- 10 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

7. SLF4J: Found binding in [jar:file:/home/robin/.m2/repository/ch/qos/logback/logback-


classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
8. SLF4J: Found binding in [jar:file:/home/robin/.m2/repository/org/jboss/slf4j/slf4j-jboss-
logging/1.2.0.Final/slf4j-jboss-logging-1.2.0.Final.jar!/org/slf4j/impl/StaticLoggerBinder.class]
9. SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
10. SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
11. 11:26:09.593 [Thread-20] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using
SLF4J as the default logging framework
12. 11:26:09.599 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent0 - -
Dio.netty.noUnsafe: false
13. 11:26:09.600 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent0 - Java version: 8
14. 11:26:09.601 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent0 -
sun.misc.Unsafe.theUnsafe: available
15. 11:26:09.601 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent0 -
sun.misc.Unsafe.copyMemory: available
16. 11:26:09.602 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent0 -
java.nio.Buffer.address: available
17. 11:26:09.603 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent0 - direct buffer
constructor: available
18. 11:26:09.604 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent0 -
java.nio.Bits.unaligned: available, true
19. 11:26:09.605 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent0 -
jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable prior to Java9
20. 11:26:09.605 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent0 -
java.nio.DirectByteBuffer.<init>(long, int): available
21. 11:26:09.606 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent - sun.misc.Unsafe:
available
22. 11:26:09.607 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent - -
Dio.netty.tmpdir: /tmp (java.io.tmpdir)
23. 11:26:09.607 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.bitMode:
64 (sun.arch.data.model)
24. 11:26:09.608 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent - -
Dio.netty.maxDirectMemory: 3704094720 bytes
25. 11:26:09.608 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent - -
Dio.netty.uninitializedArrayAllocationThreshold: -1
26. __ ____ __ _____ ___ __ ____ ______
27. --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
28. -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
29. --\___\_\____/_/ |_/_/|_/_/|_|\____/___/
30. 2020-04-02 11:26:08,775 WARN [io.qua.agr.dep.AgroalProcessor] (build-13) The Agroal
dependency is present but no JDBC datasources have been defined.
31. 11:26:09.608 [Thread-20] DEBUG io.netty.util.internal.CleanerJava6 -
java.nio.ByteBuffer.cleaner(): available
32. 11:26:09.608 [Thread-20] DEBUG io.netty.util.internal.PlatformDependent - -
Dio.netty.noPreferDirect: false
33. 11:26:09.610 [Thread-20] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId:
12872 (auto-detected)
34. 11:26:09.611 [Thread-20] DEBUG io.netty.util.NetUtil - -Djava.net.preferIPv4Stack: false
35. 11:26:09.611 [Thread-20] DEBUG io.netty.util.NetUtil - -Djava.net.preferIPv6Addresses: false
36. 11:26:09.612 [Thread-20] DEBUG io.netty.util.NetUtil - Loopback interface: lo (lo,
0:0:0:0:0:0:0:1%lo)
37. 11:26:09.613 [Thread-20] DEBUG io.netty.util.NetUtil - /proc/sys/net/core/somaxconn: 128
38. 11:26:09.613 [Thread-20] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId:
18:31:bf:ff:fe:17:85:36 (auto-detected)
39. 11:26:09.625 [main] DEBUG io.netty.util.ResourceLeakDetector - -
Dio.netty.leakDetection.level: simple
40. 11:26:09.625 [main] DEBUG io.netty.util.ResourceLeakDetector - -
Dio.netty.leakDetection.targetRecords: 4
41. 11:26:09.632 [main] DEBUG io.netty.util.internal.InternalThreadLocalMap - -
Dio.netty.threadLocalMap.stringBuilder.initialSize: 1024
42. 11:26:09.632 [main] DEBUG io.netty.util.internal.InternalThreadLocalMap - -
Dio.netty.threadLocalMap.stringBuilder.maxSize: 4096
43. 11:26:09.639 [main] DEBUG io.netty.channel.MultithreadEventLoopGroup - -
Dio.netty.eventLoopThreads: 16
44. 11:26:09.648 [main] DEBUG io.netty.channel.nio.NioEventLoop - -
Dio.netty.noKeySetOptimization: false
45. 11:26:09.649 [main] DEBUG io.netty.channel.nio.NioEventLoop - -
Dio.netty.selectorAutoRebuildThreshold: 512
46. 11:26:09.649 [main] DEBUG io.netty.util.internal.PlatformDependent - org.jctools-
core.MpscChunkedArrayQueue: available
47. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.numHeapArenas: 16

- 11 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

48. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -


Dio.netty.allocator.numDirectArenas: 16
49. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.pageSize: 8192
50. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.maxOrder: 1
51. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.chunkSize: 16384
52. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.tinyCacheSize: 512
53. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.smallCacheSize: 256
54. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.normalCacheSize: 64
55. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.maxCachedBufferCapacity: 32768
56. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.cacheTrimInterval: 8192
57. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.cacheTrimIntervalMillis: 0
58. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.useCacheForAllThreads: true
59. 11:26:09.704 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -
Dio.netty.allocator.maxCachedByteBuffersPerChunk: 1023
60. 11:26:09.809 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.ByteBufUtil - -
Dio.netty.allocator.type: pooled
61. 11:26:09.809 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.ByteBufUtil - -
Dio.netty.threadLocalDirectBufferSize: 0
62. 11:26:09.809 [vert.x-eventloop-thread-1] DEBUG io.netty.buffer.ByteBufUtil - -
Dio.netty.maxThreadLocalCharBufferSize: 16384
63. 2020-04-02 11:26:09,816 INFO [io.quarkus] (main) quarkus-tuto 0.0.1-SNAPSHOT (powered by
Quarkus 1.3.1.Final) started in 1.415s. Listening on: http://0.0.0.0:8080
64. 2020-04-02 11:26:09,818 INFO [io.quarkus] (main) Profile dev activated. Live Coding
activated.
65. 2020-04-02 11:26:09,819 INFO [io.quarkus] (main) Installed features: [agroal, cdi,
hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, narayana-jta,
resteasy, resteasy-jsonb, spring-data-jpa, spring-di]

Quakus se lance en très peu de temps alors qu'il est simplement en mode JVM classique.
Vivement le build GraalVM natif… patience.

On peut tester le service manuellement :

$ curl http://localhost:8080/api/ping/v1
pong

Si on modifie le code java et qu'on le sauvegarde, il se recompile automatiquement grâce au mode dev de Quarkus.
Par exemple : on remplace return "pong"; par return "PONG"; et on sauvegarde le fichier.

$ curl http://localhost:8080/api/ping/v1
PONG

C'est vraiment très pratique ce rechargement à chaud (live reload) !

Attention avec Lombok toutefois, Quarkus ne semble pas relancer l'annotation processor et
donc il ne génère pas le bytecode de Lombok. Lien vers cette anomalie : https://github.com/
quarkusio/quarkus/issues/4224

- 12 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

VII - Compilation en binaire avec GraalVM

En prérequis, il faut s'assurer que GraalVM est bien installé.

Je vous conseille d'utiliser pour cela SDKMAN qui est une plate-forme pour gérer plusieurs outils de développement
présents sur votre poste en plusieurs versions et vous permet de les activer simplement et rapidement, même le
temps d'une session shell (terminal).

VII-A - Installation de SDKMAN

Rien de bien compliqué, sous Linux tout du moins :

$ curl -s "https://get.sdkman.io" | bash


$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk version
SDKMAN 5.7.4+362

VII-B - Installation de GraalVM

Grâce à SDKMAN c'est vraiment simple :

1. $ sdk install java 19.3.1.r11-grl


2. Downloading: java 19.3.1.r11-grl
3. In progress...
4. 0%###################################################################### 100,0%
5. Repackaging Java 19.3.1.r11-grl...
6. Done repackaging...
7. Installing: java 19.3.1.r11-grl
8. Done installing!
9. Do you want java 19.3.1.r11-grl to be set as default? (Y/n): Y
10. Setting java 19.3.1.r11-grl as default.

Dans cet exemple, j'ai choisi de mettre GraalVM en version 19.3.1 et de le déclarer comme JDK par défaut.

GraalVM s'est installé dans le répertoire de SDKMAN /home/robin/.sdkman/candidates/java/19.3.1.r11-grl et tout a


été linké correctement pour en faire le JDK par défaut.

1. $ echo $JAVA_HOME
2. /home/robin/.sdkman/candidates/java/current
3. $ ll /home/robin/.sdkman/candidates/java/current
4. lrwxrwxrwx 1 robin robin 50 avril 2 10:25 /home/robin/.sdkman/candidates/java/current -> /
home/robin/.sdkman/candidates/java/19.3.1.r11-grl/
5. $ whereis java
6. java: /usr/bin/java /usr/lib/java /usr/share/java /home/robin/.sdkman/candidates/
java/19.3.1.r11-grl/bin/java

Pour pouvoir compiler du code Java en code natif, il faut rajouter une variable d'environnement au fichier ~/.mavenrc
(fichier à créer s'il n'existe pas).

export JAVA_HOME=/home/robin/.sdkman/candidates/java/current
export GRAALVM_HOME=$JAVA_HOME

En relançant un shell, on vérifie que tout est correctement affecté :

1. $ mvn -version
2. Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
3. Maven home: /home/robin/.sdkman/candidates/maven/current
4. Java version: 11.0.6, vendor: Oracle Corporation, runtime: /home/robin/.sdkman/candidates/
java/19.3.1.r11-grl

- 13 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

5. Default locale: fr_FR, platform encoding: UTF-8


6. OS name: "linux", version: "5.3.0-45-generic", arch: "amd64", family: "unix"

Ensuite il faut installer l'outil native-image :

1. $ gu install native-image
2. Downloading: Component catalog from www.graalvm.org
3. Processing Component: Native Image
4. Downloading: Component native-image: Native Image from github.com
5. Installing new component: Native Image (org.graalvm.native-image, version 19.3.1)

Tout est prêt pour pouvoir compiler notre application en code natif.

VII-C - Compilation en code natif

Il suffit de lancer Maven avec le profil native qui est présent dans le pom XML. C'est un peu long, c'est normal, mais
le résultat en vaut la chandelle.

1. $ mvn package -Pnative


2. ...
3. ...
4. ... (vous pouvez allez vous aérer la compilation est assez longue ...)
5. ...
6. ...
7. ...
8. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] classlist: 10 076,23 ms
9. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] (cap): 1 186,64 ms
10. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] setup: 3 325,77 ms
11. 17:25:16,623 INFO [org.hib.val.int.uti.Version] HV000001: Hibernate Validator 6.1.2.Final
12. 17:25:18,675 INFO [org.jbo.threads] JBoss Threads version 3.0.1.Final
13. 17:25:42,598 INFO [org.hib.Version] HHH000412: Hibernate ORM core version 5.4.12.Final
14. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] (typeflow): 20 083,90 ms
15. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] (objects): 16 238,17 ms
16. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] (features): 742,27 ms
17. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] analysis: 38 874,58 ms
18. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] (clinit): 658,58 ms
19. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] universe: 2 171,74 ms
20. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] (parse): 2 662,69 ms
21. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] (inline): 4 485,66 ms
22. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] (compile): 29 844,86 ms
23. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] compile: 39 558,51 ms
24. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] image: 2 916,45 ms
25. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] write: 817,69 ms
26. [quarkus-tuto-0.0.1-SNAPSHOT-runner:1965] [total]: 98 480,45 ms
27. [INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 101129ms
28. [INFO] ------------------------------------------------------------------------
29. [INFO] BUILD SUCCESS
30. [INFO] ------------------------------------------------------------------------
31. [INFO] Total time: 01:51 min
32. [INFO] Finished at: 2020-04-02T17:26:40+02:00
33. [INFO] ------------------------------------------------------------------------

La construction du binaire natif a pris presque deux minutes !

Mais on va lancer l'application qui a été générée, classiquement, dans le répertoire target :

1. $ ./target/quarkus-tuto-0.0.1-SNAPSHOT-runner
2. __ ____ __ _____ ___ __ ____ ______
3. --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
4. -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
5. --\___\_\____/_/ |_/_/|_/_/|_|\____/___/
6. 2020-04-02 17:29:01,484 INFO [io.quarkus] (main) quarkus-tuto 0.0.1-SNAPSHOT (powered by
Quarkus 1.3.1.Final) started in 0.016s. Listening on: http://0.0.0.0:8080
7. 2020-04-02 17:29:01,484 INFO [io.quarkus] (main) Profile prod activated.

- 14 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

8. 2020-04-02 17:29:01,484 INFO [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-
orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, narayana-jta, resteasy,
resteasy-jsonb, spring-data-jpa, spring-di]

Oui, c'est bien la réalité : notre application a démarré en 0.016 seconde !

Pour l'arrêter, il suffit de tuer le process ou d’effectuer un CTRL+C dans le terminal.

VIII - Configuration de l'accès aux données

VIII-A - Lancement de PostgreSQL via Docker

Dans la configuration Maven, on a paramétré une image Docker de PostgreSQL 12 qui se lance pendant la phase
de tests d'intégration.

Pendant la phase de développement, il faut donc une instance de PostgreSQL qui va fournir la base de données de
test. Je vais utiliser encore une fois Docker pour cela.

1. $ docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name quarkus_tuto


-e POSTGRES_USER=quarkus_tuto -e POSTGRES_PASSWORD=quarkus_tuto -e POSTGRES_DB=quarkus_tuto -
p 5432:5432 postgres:12.2
2. Unable to find image 'postgres:12.2' locally
3. 12.2: Pulling from library/postgres
4. c499e6d256d6: Pull complete
5. 67a768c93810: Pull complete
6. 3befaea70a64: Pull complete
7. b72dde2f70c9: Pull complete
8. 9af5f5958937: Pull complete
9. 79f4f06e2acc: Pull complete
10. bc35aa1d8687: Pull complete
11. 276504d44bd7: Pull complete
12. 56cfad4df2a4: Pull complete
13. 28bfa2f917aa: Pull complete
14. bbbebba2bc39: Pull complete
15. d2407cea5efb: Pull complete
16. 92dae474b380: Pull complete
17. c71da770d20d: Pull complete
18. Digest: sha256:d480b197ab8e01edced54cbbbba9707373473f42006468b60be04da07ce97823
19. Status: Downloaded newer image for postgres:12.2
20. The files belonging to this database system will be owned by user "postgres".
21. This user must also own the server process.
22.
23. The database cluster will be initialized with locale "en_US.utf8".
24. The default database encoding has accordingly been set to "UTF8".
25. The default text search configuration will be set to "english".
26.
27. Data page checksums are disabled.
28.
29. fixing permissions on existing directory /var/lib/postgresql/data ... ok
30. creating subdirectories ... ok
31. selecting dynamic shared memory implementation ... posix
32. selecting default max_connections ... 100
33. selecting default shared_buffers ... 128MB
34. selecting default time zone ... Etc/UTC
35. creating configuration files ... ok
36. running bootstrap script ... ok
37. performing post-bootstrap initialization ... ok
38. syncing data to disk ... ok
39. initdb: warning: enabling "trust" authentication for local connections
40. You can change this by editing pg_hba.conf or using the option -A, or
41. --auth-local and --auth-host, the next time you run initdb.
42. Success. You can now start the database server using:
43. pg_ctl -D /var/lib/postgresql/data -l logfile start

- 15 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

44. waiting for server to start....2020-04-02 15:40:28.357 UTC [47] LOG: starting
PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled by
gcc (Debian 8.3.0-6) 8.3.0, 64-bit
45. 2020-04-02 15:40:28.360 UTC [47] LOG: listening on Unix socket "/var/run/
postgresql/.s.PGSQL.5432"
46. 2020-04-02 15:40:28.383 UTC [48] LOG: database system was shut down at 2020-04-02 15:40:28
UTC
47. 2020-04-02 15:40:28.389 UTC [47] LOG: database system is ready to accept connections
48. done
49. server started
50. CREATE DATABASE
51. /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
52. 2020-04-02 15:40:28.603 UTC [47] LOG: received fast shutdown request
53. waiting for server to shut down....2020-04-02 15:40:28.605 UTC [47] LOG: aborting any active
transactions
54. 2020-04-02 15:40:28.608 UTC [47] LOG: background worker "logical replication
launcher" (PID 54) exited with exit code 1
55. 2020-04-02 15:40:28.609 UTC [49] LOG: shutting down
56. 2020-04-02 15:40:28.633 UTC [47] LOG: database system is shut down
57. done
58. server stopped
59. PostgreSQL init process complete; ready for start up.
60. 2020-04-02 15:40:28.739 UTC [1] LOG: starting PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) on
x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
61. 2020-04-02 15:40:28.740 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
62. 2020-04-02 15:40:28.740 UTC [1] LOG: listening on IPv6 address "::", port 5432
63. 2020-04-02 15:40:28.744 UTC [1] LOG: listening on Unix socket "/var/run/
postgresql/.s.PGSQL.5432"
64. 2020-04-02 15:40:28.764 UTC [65] LOG: database system was shut down at 2020-04-02 15:40:28
UTC
65. 2020-04-02 15:40:28.770 UTC [1] LOG: database system is ready to accept connections

Laissons PostgreSQL fonctionner dans son terminal.

VIII-B - Paramétrage de l'application

Il faut créer un fichier application.properties comme ressource du projet Maven dans la partie Java.

1. # CDI
2. quarkus.arc.remove-unused-beans=false
3. %dev.quarkus.smallrye-openapi.path=/openapi
4. %dev.quarkus.swagger-ui.always-include=true
5. %dev.quarkus.swagger-ui.path=/openapi-ui
6. # DEV with PostgreSQL
7. %dev.quarkus.datasource.db-kind=postgresql
8. %dev.quarkus.datasource.jdbc.url=jdbc:postgresql:quarkus_tuto
9. %dev.quarkus.datasource.username=quarkus_tuto
10. %dev.quarkus.datasource.password=quarkus_tuto
11. %dev.quarkus.datasource.jdbc.max-size=8
12. %dev.us.datasource.jdbc.min-size=2
13. %dev.quarkus.hibernate-orm.log.sql=false
14. %dev.quarkus.hibernate-orm.database.generation=drop-and-create
15. %dev.quarkus.hibernate-orm.sql-load-script=import.sql
16. %dev.quarkus.log.level=INFO
17. %dev.quarkus.log.category."org.hibernate".level=INFO
18. %dev.quarkus.log.category."fr.fxjavadevblog".level=DEBUG
19. # PROD
20. %prod.quarkus.datasource.db-kind=postgresql
21. %prod.quarkus.datasource.jdbc.url=jdbc:postgresql:quarkus_tuto
22. %prod.quarkus.datasource.username=quarkus_tuto
23. %prod.quarkus.datasource.password=quarkus_tuto
24. %prod.quarkus.hibernate-orm.database.generation=drop-and-create
25. %prod.quarkus.hibernate-orm.sql-load-script=import.sql

Remarque importante : quand on construit l'image native de l'application, Quarkus se met


automatiquement en mode prod. Cela signifie que certains paramètres sont ignorés par défaut

- 16 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

comme le drop-and-create et le sql-load-script. C'est une très bonne pratique, cependant,


dans le cadre de ce tutoriel où les données ne persistent pas, je force, même en mode prod,
la création de la base de données et l'import du script SQL. Dans le fichier ci-dessus, ce sont
les lignes %prod.* qui s'activent en production AUSSI. Je le redis : à ne pas faire dans un
vrai projet !

Pour les tests unitaires et les tests d'intégration, nous aurons donc des fichiers application.properties différents.

IX - Des entités à rendre persistantes

Bien évidemment, il nous faut au moins une classe persistante. J'ai repris ici des exemples d'un précédent article :

• VideoGame : classe persistante JPA ;


• Genre : une enum JAVA persistante sous forme de String ;
• Producers : des producers CDI pour les UUID qui serviront de @Id dans la classe persistante comme clef
primaire ;
• VideoGameReposity : le CRUD issu de Spring Data JPA ;
• VideoGameFactory : de quoi créer des instances de la classe VideoGame en bénéficiant de l'injection
automatique de UUID.

IX-A - VideoGame et Genre

1. package fr.fxjavadevblog.qjg.videogame;
2.
3. import java.io.Serializable;
4.
5. import javax.enterprise.context.Dependent;
6. import javax.inject.Inject;
7. import javax.persistence.Column;
8. import javax.persistence.Entity;
9. import javax.persistence.EnumType;
10. import javax.persistence.Enumerated;
11. import javax.persistence.Id;
12. import javax.persistence.Table;
13. import javax.persistence.Version;
14. import fr.fxjavadevblog.qjg.utils.InjectUUID;
15. import lombok.AccessLevel;
16. import lombok.EqualsAndHashCode;
17. import lombok.Getter;
18. import lombok.NoArgsConstructor;
19. import lombok.Setter;
20. import lombok.ToString;
21. @SuppressWarnings("serial")
22. // Lombok
23. @NoArgsConstructor(access = AccessLevel.PROTECTED)
24. @EqualsAndHashCode(of = "id")
25. @ToString(of = { "id", "name" })
26. // CDI Annotation
27. @Dependent
28. // JPA Annotation
29. @Entity
30. @Table(name = "VIDEO_GAME")
31. public class VideoGame implements Serializable
32. {
33. @Id
34. @Inject
35. @InjectUUID
36. @Getter
37. @Column(length = 36)
38. private String id;
39. @Getter
40. @Setter

- 17 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

41. @Column(name = "NAME", nullable = false, unique = true)


42. private String name;
43. @Getter
44. @Setter
45. @Enumerated(EnumType.STRING)
46. @Column(name = "GENRE", nullable = false)
47. private Genre genre;
48. @Version
49. @Getter
50. @Column(name = "VERSION")
51. private Long version;
52. }

1. /**
2. * Enumeration of genres of Video Games for Atari ST.
3. *
4. * @author François-Xavier Robin
5. *
6. */
7. public enum Genre
8. {
9. ARCADE, EDUCATION, FIGHTING, PINBALL, PLATFORM, REFLEXION, RPG, SHOOT_THEM_UP, SIMULATION,
SPORT;
10. }

IX-B - Producers CDI et annotation

L'annotation @InjectUUID est utilisée sur la classe VideoGame.

1. package fr.fxjavadevblog.qjg;
2. import java.lang.annotation.ElementType;
3. import java.lang.annotation.Retention;
4. import java.lang.annotation.RetentionPolicy;
5. import java.lang.annotation.Target;
6. import javax.inject.Qualifier;
7. /**
8. * annotation to mark a field to be injected by CDI with a UUID.
9. *
10. * @author François-Xavier Robin
11. *
12. */
13. @Qualifier
14. @Retention(RetentionPolicy.RUNTIME)
15. @Target({ElementType.FIELD, ElementType.METHOD})
16. public @interface InjectUUID
17. {
18. }

Et son « traitement » par le Producer CDI suivant :

1. package fr.fxjavadevblog.qjg.utils;
2. import java.util.UUID;
3. import javax.enterprise.context.ApplicationScoped;
4. import javax.enterprise.inject.Produces;
5. @ApplicationScoped
6. public class Producers
7. {
8. /**
9. * produces randomly generated UUID for primary keys.
10. *
11. * @return UUID as a HEXA-STRING
12. *
13. */
14. @Produces
15. @InjectUUID
16. public String produceUUIDAsString()
17. {

- 18 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

18. return UUID.randomUUID().toString();


19. }
20. }

IX-C - Le repository CRUD avec Spring Data JPA

1. package fr.fxjavadevblog.qjg.videogame;
2. import java.util.List;
3. import org.springframework.data.repository.CrudRepository;
4. /**
5. * CRUD repository for the VideoGame class. Primary key is a UUID represented by a String.
6. * This repository is created by Hibernate Data JPA.
7. *
8. * @author François-Xavier Robin
9. *
10. */
11. public interface VideoGameRepository extends CrudRepository<VideoGame, String>
12. {
13. /**
14. * gets every Video Game filtered by the given Genre.
15. *
16. * @param genre
17. * genre of video game
18. * @see Genre
19. *
20. * @return
21. * a list of video games
22. */
23. List<VideoGame> findByGenre(Genre genre);
24. }

X - Le endpoint REST JAX-RS

Et enfin de quoi servir le tout sur HTTP avec JAX-WS qui répond à l'URL /api/videogames/v1 :

1. package fr.fxjavadevblog.qjg.videogame;
2. import java.util.List;
3. import javax.ws.rs.BadRequestException;
4. import javax.ws.rs.GET;
5. import javax.ws.rs.Path;
6. import javax.ws.rs.PathParam;
7. import javax.ws.rs.Produces;
8. /**
9. * JAX-RS endpoint for Video Games.
10. *
11. * @author François-Xavier Robin
12. *
13. */
14. @Path("/api/videogames/v1")
15. public class VideoGameResource
16. {
17. private final VideoGameRepository videoGameRepository;
18. public VideoGameResource(VideoGameRepository videoGameRepository)
19. {
20. this.videoGameRepository = videoGameRepository;
21. }
22. @GET
23. @Produces("application/json")
24. public Iterable<VideoGame> findAll()
25. {
26. return videoGameRepository.findAll();
27. }
28. @GET
29. @Path("/genre/{genre}")
30. @Produces("application/json")
31. public List<VideoGame> findByGenre(@PathParam(value = "genre") String genre)
32. {

- 19 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

33. try
34. {
35. Genre convertedGenre = Genre.valueOf(genre.replace("-", "_").toUpperCase());
36. return videoGameRepository.findByGenre(convertedGenre);
37. }
38. catch (java.lang.IllegalArgumentException e)
39. {
40. throw new BadRequestException("Genre " + genre + "does not exist.");
41. }
42. }
43. }

Il suffit ensuite de déclencher la requête REST suivante pour obtenir tous les jeux vidéo :

1. $ curl http://localhost:8080//api/videogames/v1
2. [
3. {
4. "id": "896b9c77-4f6d-4bd6-b681-2791acfa0d51",
5. "name": "100 4 1",
6. "genre": "REFLEXION",
7. "version": 1
8. },
9. {
10. "id": "6bf157fa-bd95-46ce-bbca-58afb87ebb9b",
11. "name": "10TH FRAME",
12. "genre": "SPORT",
13. "version": 1
14. },
15. {
16. "id": "e603e430-0853-46b0-9f44-d4f662f56c51",
17. "name": "1943 : THE BATTLE OF MIDWAY",
18. "genre": "SHOOT_THEM_UP",
19. "version": 1
20. },
21. {
22. "id": "61ec5869-d9f5-497a-9ffc-8e3612892c4b",
23. "name": "1ST DIVISION MANAGER",
24. "genre": "SPORT",
25. "version": 1
26. },
27. {
28. "id": "ed1233c4-c130-49f4-b329-314a6dd957a3",
29. "name": "1ST SERVE TENNIS",
30. "genre": "SPORT",
31. "version": 1
32. },
33. {
34. "id": "85d071ca-95bc-488a-afdc-494c430f03d9",
35. "name": "20000 LEAGUES UNDER THE SEA",
36. "genre": "RPG",
37. "version": 1
38. },
39. ... etc ...

De la même manière, pour filtrer sur le genre de jeu :

1. $ curl http://localhost:8080/api/videogames/v1/genre/SHOOT_THEM_UP
2. [
3. {
4. "id": "e603e430-0853-46b0-9f44-d4f662f56c51",
5. "name": "1943 : THE BATTLE OF MIDWAY",
6. "genre": "SHOOT_THEM_UP",
7. "version": 1
8. },
9. {
10. "id": "fa02815b-f6d2-4ba1-9f63-5c8f575d06b6",
11. "name": "AIR SUPPLY",
12. "genre": "SHOOT_THEM_UP",
13. "version": 1
14. },

- 20 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

15. {
16. "id": "07638287-8f45-4be8-a887-c57f350a9ce7",
17. "name": "ALBEDO",
18. "genre": "SHOOT_THEM_UP",
19. "version": 1
20. },
21. {
22. "id": "56417168-1e57-4197-a490-56258e5405eb",
23. "name": "ALCON",
24. "genre": "SHOOT_THEM_UP",
25. "version": 1
26. },
27. {
28. "id": "d5bd6aaa-674d-4e7f-ae8e-53118de897c6",
29. "name": "ALIEN BLAST",
30. "genre": "SHOOT_THEM_UP",
31. "version": 1
32. },
33. {
34. "id": "2589d502-4619-4728-b688-9cece2a8a3ab",
35. "name": "ALIEN BUSTERS IV",
36. "genre": "SHOOT_THEM_UP",
37. "version": 1
38. }
39. ... etc ...

Cela fonctionne, mais je trouve que ce n'est pas satisfaisant concernant les URL d'invocation pour les genres, ainsi
que le résultat JSON qui sérialise en majuscules les valeurs de l'enum. C'est normal, mais ce n'est pas très « HTTP
Friendly ».

On va y remédier dans le paragraphe qui suit !

XI - Convertisseur générique pour les valeurs de l'enum

Pour résumer le problème, les URL d'appel ainsi que le contenu du retour JSON ne respectent pas les conventions
classiques de nommage de REST.

En plus, les manipulations comme Genre.valueOf(genre.replace("-", "_").toUpperCase()); ne


sont pas très jolies, à mon goût.

Ce que l'on souhaite pour les URL d'appel et les retours JSON :

• utiliser des « - » au lieu des « _ » pour séparer les mots clefs ;


• basculer tout en minuscules.

Vous pouvez retrouver ces recommandations ici : https://restfulapi.net/resource-naming/

Exemple : l'appel de /api/videogames/v1/genre/shoot-them-up doit retourner :

1. [
2. ...
3. ...
4. {
5. "id": "d5bd6aaa-674d-4e7f-ae8e-53118de897c6",
6. "name": "ALIEN BLAST",
7. "genre": "shoot-them-up",
8. "version": 1
9. },
10. ...
11. ...
12. ]

- 21 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

Mais, on ne veut pas toucher aux conventions de nommage de l'enum Genre ! C'est du Java et on doit pouvoir garder
les choses ainsi !

Il y a plein de solutions pour cela. Celle que je vais privilégier est l'usage de l'annotation @JsonProperty de Jackson
que l'on va placer sur chacune des valeurs de l'enum :

1. public enum Genre


2. {
3. @JsonProperty(value = "arcade")
4. ARCADE,
5.
6. @JsonProperty(value = "education")
7. EDUCATION,
8. @JsonProperty(value = "fighting")
9. FIGHTING,
10. @JsonProperty(value = "pinball")
11. PINBALL,
12. @JsonProperty(value = "platform")
13. PLATFORM,
14. @JsonProperty(value = "reflexion")
15. REFLEXION,
16. @JsonProperty(value = "rpg")
17. RPG,
18. @JsonProperty(value = "shoot-them-up")
19. SHOOT_THEM_UP,
20. @JsonProperty(value = "simulation")
21. SIMULATION,
22. @JsonProperty(value = "sport")
23. SPORT;
24. }

Avec cela, on règle déjà un premier problème : le contenu du retour JSON !

1. $ curl http://localhost:8080//api/videogames/v1/genre/SHOOT_THEM_UP
2. [
3. {
4. "id": "e603e430-0853-46b0-9f44-d4f662f56c51",
5. "name": "1943 : THE BATTLE OF MIDWAY",
6. "genre": "shoot-them-up",
7. "version": 1
8. },
9. ... etc ...

Mais le problème persiste pour l'URL ! Il faut donc créer un ParamConverter JAX-RS !

Un quoi ?

Un ParamConverter<T> est un convertisseur JAX-RS qui va être invoqué à différents moments du traitement de la
requête. Son travail est de fournir une conversion bidirectionnelle de <T> vers String et de String vers <T>.

Mais on va en créer un générique qui va aller chercher la valeur de l'annotation Jackson @JsonProperty.

1. package fr.fxjavadevblog.qjg.utils;
2.
3. import java.util.EnumSet;
4. import java.util.Optional;
5.
6. import javax.ws.rs.ext.ParamConverter;
7.
8. import org.slf4j.Logger;
9. import org.slf4j.LoggerFactory;
10.
11. import com.fasterxml.jackson.annotation.JsonProperty;
12. import com.google.common.collect.BiMap;
13. import com.google.common.collect.HashBiMap;

- 22 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

14.
15. /**
16. * Generic JAX-RS Enum converter based on the Jackson JsonProperty annotation to
17. * get the mapping.
18. *
19. * @author François-Xavier Robin
20. *
21. * @param <T>
22. */
23.
24. public class GenericEnumConverter<T extends Enum<T>> implements ParamConverter<T>
25. {
26. private static final Logger log = LoggerFactory.getLogger(GenericEnumConverter.class);
27. /**
28. * bi-directionnal Map to store enum value as key and its string representation as value.
29. * The string representation is retrieved through the JsonProperty annotation put on the
enum constant.
30. */
31. private final BiMap<T, String> biMap = HashBiMap.create();
32. /**
33. * returns a Generic converter for an enum class in the context of JAX-RS ParamConverter.
34. *
35. * @param <T>
36. * enum type
37. * @param t
38. * enum type class
39. * @return
40. * a generic converter used by JAX-RS.
41. */
42. public static <T extends Enum<T>> ParamConverter<T> of(Class<T> t)
43. {
44. return new GenericEnumConverter<>(t);
45. }
46. private GenericEnumConverter(Class<T> t)
47. {
48. log.debug("Generating conversion map for enum {}", t);
49. EnumSet.allOf(t).forEach(v -> {
50. try
51. {
52. String enumValue = v.name();
53. JsonProperty annotation =
v.getClass().getDeclaredField(enumValue).getAnnotation(JsonProperty.class);
54. // get the annotation if it exists or take the classic enum representation
55. String result =
Optional.ofNullable(annotation).map(JsonProperty::value).orElse(enumValue);
56. log.debug("Enum value {}.{} is mapped to \"{}\"", t.getSimpleName(),
v.name(), result);
57. biMap.put(v, result);
58. }
59. catch (NoSuchFieldException | SecurityException e)
60. {
61. log.error("Error while populating BiMap of enum {}", t.getClass());
62. log.error("Thrown by ", e);
63. }
64. });
65. }
66. /**
67. * returns the enum type constant from this string representation.
68. */
69. @Override
70. public T fromString(String value)
71. {
72. T returnedValue = biMap.inverse().get(value);
73. log.debug("Converting String \"{}\" to {}.{}", value, returnedValue.getClass(),
returnedValue);
74. return returnedValue;
75. }
76. /**
77. * returns the string represenation from this enum type constant.
78. */
79. @Override
80. public String toString(T t)

- 23 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

81. {
82. String returnedValue = biMap.get(t);
83. log.debug("Converting Enum {}.{} to String \"{}\"", t.getClass(), t, returnedValue);
84. return returnedValue;
85. }
86. }

Les concepts de cette classe sont les suivants :

• elle est instanciée en prenant une enum comme argument : ParamConverter<Genre> converter =
GenericEnumConverter.of(Genre.class); ;
• elle introspecte l'enum pendant sa construction à la recherche des annotations @JsonProperty sur chaque
valeur ;
• pour chaque valeur, elle récupère le contenu de l'annotation @JsonProperty et peuple une BiMap (Map
bidirectionnelle Guava, incluse dans Quarkus) ;
• si l'annotation n'est pas présente (on ne sait jamais), la valeur toString() de l'enum sera prise par défaut.

La partie « générique » permet de s'adapter à n'importe quelle enum.

À la fin de la construction de cette classe, la BiMap contient les tuples suivants :

1. Generating conversion map for enum class fr.fxjavadevblog.qjg.genre.Genre


2. Enum value Genre.ARCADE is mapped to "arcade"
3. Enum value Genre.EDUCATION is mapped to "education"
4. Enum value Genre.FIGHTING is mapped to "fighting"
5. Enum value Genre.PINBALL is mapped to "pinball"
6. Enum value Genre.PLATFORM is mapped to "platform"
7. Enum value Genre.REFLEXION is mapped to "reflexion"
8. Enum value Genre.RPG is mapped to "rpg"
9. Enum value Genre.SHOOT_THEM_UP is mapped to "shoot-them-up"
10. Enum value Genre.SIMULATION is mapped to "simulation"
11. Enum value Genre.SPORT is mapped to "sport"

Ensuite, il faut enregistrer ce convertisseur automatique auprès de JAX-RS : cela se fait au moyen d'une classe qui
implémente l'interface ParamConverterProvider et d'une annotation @Provider :

1. package fr.fxjavadevblog.qjg.genre;
2. import java.lang.annotation.Annotation;
3. import java.lang.reflect.Type;
4. import javax.ws.rs.ext.ParamConverter;
5. import javax.ws.rs.ext.ParamConverterProvider;
6. import javax.ws.rs.ext.Provider;
7. import org.slf4j.Logger;
8. import org.slf4j.LoggerFactory;
9. import fr.fxjavadevblog.qjg.utils.GenericEnumConverter;
10. /**
11. * JAX-RS provider for Genre conversion. This converter is registered because of
12. * the Provider annotation.
13. *
14. * @author François-Xavier Robin
15. */
16. @Provider
17. public class GenreConverterProvider implements ParamConverterProvider
18. {
19. private final Logger log = LoggerFactory.getLogger(GenreConverterProvider.class);
20. private final ParamConverter<Genre> converter = GenericEnumConverter.of(Genre.class);
21. @SuppressWarnings("unchecked")
22. @Override
23. public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType,
Annotation[] annotations)
24. {
25. log.debug("Registering GenreConverter");
26. return (Genre.class.equals(rawType)) ? (ParamConverter<T>) converter : null;
27. }
28. }

- 24 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

Le endpoint REST de la classe VideoGameResource change légèrement pour se simplifier :

1. @GET
2. @Path("/genre/{genre}")
3. public List<VideoGame> findByGenre(@PathParam("genre") Genre genre)
4. {
5. return videoGameRepository.findByGenre(genre);
6. }

Notez ici l'appel au convertisseur générique précédemment codé.

Grâce à tout ceci, on obtient le comportement souhaité :

1. $ curl http://localhost:8080//api/videogames/v1/genre/shoot-them-up
2. [
3. {
4. "id": "e603e430-0853-46b0-9f44-d4f662f56c51",
5. "name": "1943 : THE BATTLE OF MIDWAY",
6. "genre": "shoot-them-up",
7. "version": 1
8. },
9. {
10. "id": "fa02815b-f6d2-4ba1-9f63-5c8f575d06b6",
11. "name": "AIR SUPPLY",
12. "genre": "shoot-them-up",
13. "version": 1
14. },
15. {
16. "id": "07638287-8f45-4be8-a887-c57f350a9ce7",
17. "name": "ALBEDO",
18. "genre": "shoot-them-up",
19. "version": 1
20. },
21. {
22. "id": "56417168-1e57-4197-a490-56258e5405eb",
23. "name": "ALCON",
24. "genre": "shoot-them-up",
25. "version": 1
26. },
27. {
28. "id": "d5bd6aaa-674d-4e7f-ae8e-53118de897c6",
29. "name": "ALIEN BLAST",
30. "genre": "shoot-them-up",
31. "version": 1
32. },
33. {
34. "id": "2589d502-4619-4728-b688-9cece2a8a3ab",
35. "name": "ALIEN BUSTERS IV",
36. "genre": "shoot-them-up",
37. "version": 1
38. }
39. ... etc.

De plus, quand on invoque l'URL avec un genre qui ne peut pas être mappé, on obtient une erreur 404. Ce
comportement est très satisfaisant.

1. $ curl -s -o /dev/null -D - http://localhost:8080//api/videogames/v1/genre/SHOOT_THEM_UP


2. HTTP/1.1 404 Not Found
3. Content-Length: 0
4. Content-Type: application/json

- 25 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

XII - Tests

XII-A - Tests unitaires

Ces tests sont classiquement dans le répertoire « test ». Les points particuliers sont les suivants :

• la base de données n'est pas créée dans ce cas ;


• @QuarkusTest est présent sur quelques classes de tests unitaires pour vérifier le comportement de CDI ;
• les tests unitaires nommés IT_* sont ignorés par convention (tests d'intégration) ;
• le profil test de Quarkus est automatique, un fichier spécifique application.properties est utilisé à cet effet ;
• la propriété quarkus.arc.remove-unused-beans=false permet de conserver dans le contexte CDI tous les
beans injectables.

Pour lancer les tests unitaires, procédez comme ci-dessous :

1. $ mvn clean test


2. ...
3. ...
4. [INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ quarkus-tuto ---
5. [INFO]
6. [INFO] -------------------------------------------------------
7. [INFO] T E S T S
8. [INFO] -------------------------------------------------------
9. [INFO] Running fr.fxjavadevblog.qjg.ping.PingTest
10. 2020-04-14 19:25:27,022 INFO [io.quarkus] (main) Quarkus 1.3.1.Final started in 4.701s.
Listening on: http://0.0.0.0:8081
11. 2020-04-14 19:25:27,030 INFO [io.quarkus] (main) Profile test activated.
12. 2020-04-14 19:25:27,031 INFO [io.quarkus] (main) Installed features: [agroal, cdi,
hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, narayana-jta,
resteasy, resteasy-jackson, smallrye-openapi, spring-data-jpa, spring-di, swagger-ui]
13. [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.947 s - in
fr.fxjavadevblog.qjg.ping.PingTest
14. [INFO] Running fr.fxjavadevblog.qjg.utils.GenericEnumConverterTest
15. [INFO] Tests run: 100, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.174 s - in
fr.fxjavadevblog.qjg.utils.GenericEnumConverterTest
16. [INFO] Running fr.fxjavadevblog.qjg.utils.CDITest
17. [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 s - in
fr.fxjavadevblog.qjg.utils.CDITest
18. [INFO] Running fr.fxjavadevblog.qjg.utils.DummyTest
19. [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 s - in
fr.fxjavadevblog.qjg.utils.DummyTest
20. 2020-04-14 19:25:28,697 INFO [io.quarkus] (main) Quarkus stopped in 0.056s
21. [INFO]
22. [INFO] Results:
23. [INFO]
24. [INFO] Tests run: 105, Failures: 0, Errors: 0, Skipped: 0
25. [INFO]
26. [INFO] ------------------------------------------------------------------------
27. [INFO] BUILD SUCCESS
28. [INFO] ------------------------------------------------------------------------
29. [INFO] Total time: 13.888 s
30. [INFO] Finished at: 2020-04-14T19:25:29+02:00
31. [INFO] ------------------------------------------------------------------------

XII-B - Tests d'intégration

Ces tests sont placés dans le répertoire « test-integration ». Les points particuliers sont les suivants :

• une image Docker PostgreSQL pour les tests d'intégration est lancée ;
• @QuarkusTest est présent sur tous les tests afin de disposer de l'environnement complet ;
• Les tests sont réalisés avec Rest Assured.

- 26 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

1. package fr.fxjavadevblog.qjg.videogame;
2. import static io.restassured.RestAssured.given;
3. import static org.hamcrest.CoreMatchers.containsString;
4. import org.junit.jupiter.api.DisplayName;
5. import org.junit.jupiter.api.Tag;
6. import org.junit.jupiter.api.Test;
7. import fr.fxjavadevblog.qjg.global.TestingGroups;
8. import io.quarkus.test.junit.QuarkusTest;
9. @QuarkusTest
10. @Tag(TestingGroups.INTEGRATION_TESTING)
11. class IT_VideoGameResource
12. {
13. public static final String ENDPOINT = "/api/videogames/v1";
14. @Test
15. @DisplayName("Get " + ENDPOINT)
16. void testGetAllVideoGames()
17. {
18. given().when()
19. .get(ENDPOINT + "/")
20. .then()
21. .statusCode(200)
22. .assertThat().body(containsString("XENON"),
23. containsString("RICK"),
24. containsString("LOTUS"));
25. }
26. }

Pour lancer les tests d'intégration :

• s'assurer que le PostgreSQL de dev n'est pas lancé ;


• lancer la commande $ mvn -Dskip.surefire.tests verify.

1. $ mvn -Dskip.surefire.tests verify


2. Scanning for projects...
3. [INFO]
4. [INFO] -------------------< fr.fxjavadevblog:quarkus-tuto >--------------------
5. [INFO] Building Quarkus-JPA-PostgreSQL 0.0.1-SNAPSHOT
6. [INFO] --------------------------------[ jar ]---------------------------------
7. [INFO]
8. [INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ quarkus-tuto ---
9. [INFO] Using 'UTF-8' encoding to copy filtered resources.
10. [INFO] Copying 2 resources
11. [INFO]
12. [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ quarkus-tuto ---
13. [INFO] Nothing to compile - all classes are up to date
14. [INFO]
15. [INFO] --- build-helper-maven-plugin:3.1.0:add-test-source (add-integration-test-sources) @
quarkus-tuto ---
16. [INFO] Test Source directory: /home/robin/git/quarkus-tuto/src/test-integration/java added.
17. [INFO]
18. [INFO] --- build-helper-maven-plugin:3.1.0:add-test-resource (add-integration-test-resource)
@ quarkus-tuto ---
19. [INFO]
20. [INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ quarkus-tuto
---
21. [INFO] Using 'UTF-8' encoding to copy filtered resources.
22. [INFO] Copying 1 resource
23. [INFO] Copying 1 resource
24. [INFO]
25. [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ quarkus-tuto ---
26. [INFO] Nothing to compile - all classes are up to date
27. [INFO]
28. [INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ quarkus-tuto ---
29. [INFO] Tests are skipped.
30. [INFO]
31. [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ quarkus-tuto ---
32. [INFO]
33. [INFO] --- quarkus-maven-plugin:1.3.1.Final:build (default) @ quarkus-tuto ---
34. [INFO] [org.jboss.threads] JBoss Threads version 3.0.1.Final
35. [INFO] [org.hibernate.Version] HHH000412: Hibernate ORM core version 5.4.12.Final

- 27 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

36. [INFO] [io.quarkus.deployment.pkg.steps.JarResultBuildStep] Building thin jar: /home/robin/


git/quarkus-tuto/target/quarkus-tuto-0.0.1-SNAPSHOT-runner.jar
37. [INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 1647ms
38. [INFO]
39. [INFO] --- maven-resources-plugin:3.1.0:copy-resources (copy-resources) @ quarkus-tuto ---
40. [INFO] Using 'UTF-8' encoding to copy filtered resources.
41. [INFO] Copying 1 resource
42. [INFO]
43. [INFO] --- docker-maven-plugin:0.33.0:stop (docker:start) @ quarkus-tuto ---
44. [INFO]
45. [INFO] --- docker-maven-plugin:0.33.0:start (docker:start) @ quarkus-tuto ---
46. [INFO] DOCKER> [postgres:12.2] "postgresql": Start container ca6a77a7d1e3
47. [INFO] DOCKER> [postgres:12.2] "postgresql": Waiting for mapped ports [5432] on host
localhost
48. [INFO] DOCKER> [postgres:12.2] "postgresql": Waited on tcp
port '[localhost/127.0.0.1:5432]' 4 ms
49. [INFO]
50. [INFO] --- maven-failsafe-plugin:2.22.2:integration-test (default) @ quarkus-tuto ---
51. 19:29:19.154 PostgreSQL Server :The files belonging to this database system will be owned by
user "postgres".
52. 19:29:19.154 PostgreSQL Server :This user must also own the server process.
53. 19:29:19.154 PostgreSQL Server :
54. 19:29:19.154 PostgreSQL Server :The database cluster will be initialized with
locale "en_US.utf8".
55. 19:29:19.154 PostgreSQL Server :The default database encoding has accordingly been set
to "UTF8".
56. 19:29:19.154 PostgreSQL Server :The default text search configuration will be set
to "english".
57. 19:29:19.154 PostgreSQL Server :
58. 19:29:19.154 PostgreSQL Server :Data page checksums are disabled.
59. 19:29:19.154 PostgreSQL Server :
60. 19:29:19.154 PostgreSQL Server :fixing permissions on existing directory /var/lib/postgresql/
data ... ok
61. 19:29:19.155 PostgreSQL Server :creating subdirectories ... ok
62. 19:29:19.155 PostgreSQL Server :selecting dynamic shared memory implementation ... posix
63. 19:29:19.171 PostgreSQL Server :selecting default max_connections ... 100
64. 19:29:19.197 PostgreSQL Server :selecting default shared_buffers ... 128MB
65. 19:29:19.243 PostgreSQL Server :selecting default time zone ... Etc/UTC
66. 19:29:19.244 PostgreSQL Server :creating configuration files ... ok
67. [INFO]
68. [INFO] -------------------------------------------------------
69. [INFO] T E S T S
70. [INFO] -------------------------------------------------------
71. 19:29:19.393 PostgreSQL Server :running bootstrap script ... ok
72. 19:29:20.011 PostgreSQL Server :performing post-bootstrap initialization ... ok
73. [INFO] Running fr.fxjavadevblog.qjg.utils.IT_DummyTest
74. [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.032 s - in
fr.fxjavadevblog.qjg.utils.IT_DummyTest
75. [INFO] Running fr.fxjavadevblog.qjg.videogame.IT_VideoGameResource
76. 19:29:20.174 PostgreSQL Server :syncing data to disk ... ok
77. 19:29:20.174 PostgreSQL Server :
78. 19:29:20.174 PostgreSQL Server :
79. 19:29:20.174 PostgreSQL Server :Success. You can now start the database server using:
80. 19:29:20.174 PostgreSQL Server :
81. 19:29:20.174 PostgreSQL Server : pg_ctl -D /var/lib/postgresql/data -l logfile start
82. 19:29:20.174 PostgreSQL Server :
83. 19:29:20.174 PostgreSQL Server :initdb: warning: enabling "trust" authentication for local
connections
84. 19:29:20.174 PostgreSQL Server :You can change this by editing pg_hba.conf or using the
option -A, or
85. 19:29:20.174 PostgreSQL Server :--auth-local and --auth-host, the next time you run initdb.
86. 19:29:20.201 PostgreSQL Server :waiting for server to start....2020-04-14 17:29:20.201 UTC
[47] LOG: starting PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled
by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
87. 19:29:20.203 PostgreSQL Server :2020-04-14 17:29:20.203 UTC [47] LOG: listening on Unix
socket "/var/run/postgresql/.s.PGSQL.5432"
88. 19:29:20.218 PostgreSQL Server :2020-04-14 17:29:20.218 UTC [48] LOG: database system was
shut down at 2020-04-14 17:29:19 UTC
89. 19:29:20.223 PostgreSQL Server :2020-04-14 17:29:20.223 UTC [47] LOG: database system is
ready to accept connections
90. 19:29:20.292 PostgreSQL Server : done
91. 19:29:20.292 PostgreSQL Server :server started

- 28 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

92. 19:29:20.418 PostgreSQL Server :CREATE DATABASE


93. 19:29:20.419 PostgreSQL Server :
94. 19:29:20.419 PostgreSQL Server :
95. 19:29:20.419 PostgreSQL Server :/usr/local/bin/docker-entrypoint.sh: ignoring /docker-
entrypoint-initdb.d/*
96. 19:29:20.419 PostgreSQL Server :
97. 19:29:20.420 PostgreSQL Server :2020-04-14 17:29:20.420 UTC [47] LOG: received fast shutdown
request
98. 19:29:20.422 PostgreSQL Server :waiting for server to shut down....2020-04-14 17:29:20.422
UTC [47] LOG: aborting any active transactions
99. 19:29:20.423 PostgreSQL Server :2020-04-14 17:29:20.423 UTC [47] LOG: background
worker "logical replication launcher" (PID 54) exited with exit code 1
100. 19:29:20.424 PostgreSQL Server :2020-04-14 17:29:20.424 UTC [49] LOG: shutting down
101. 19:29:20.437 PostgreSQL Server :2020-04-14 17:29:20.436 UTC [47] LOG: database system is
shut down
102. 19:29:20.520 PostgreSQL Server : done
103. 19:29:20.520 PostgreSQL Server :server stopped
104. 19:29:20.520 PostgreSQL Server :
105. 19:29:20.520 PostgreSQL Server :PostgreSQL init process complete; ready for start up.
106. 19:29:20.520 PostgreSQL Server :
107. 19:29:20.537 PostgreSQL Server :2020-04-14 17:29:20.537 UTC [1] LOG: starting
PostgreSQL 12.2 (Debian 12.2-2.pgdg100+1) on x86_64-pc-linux-gnu, compiled by
gcc (Debian 8.3.0-6) 8.3.0, 64-bit
108. 19:29:20.538 PostgreSQL Server :2020-04-14 17:29:20.537 UTC [1] LOG: listening on IPv4
address "0.0.0.0", port 5432
109. 19:29:20.538 PostgreSQL Server :2020-04-14 17:29:20.537 UTC [1] LOG: listening on IPv6
address "::", port 5432
110. 19:29:20.541 PostgreSQL Server :2020-04-14 17:29:20.541 UTC [1] LOG: listening on Unix
socket "/var/run/postgresql/.s.PGSQL.5432"
111. 19:29:20.555 PostgreSQL Server :2020-04-14 17:29:20.555 UTC [65] LOG: database system was
shut down at 2020-04-14 17:29:20 UTC
112. 19:29:20.560 PostgreSQL Server :2020-04-14 17:29:20.560 UTC [1] LOG: database system is
ready to accept connections
113. Hibernate:
114.
115. drop table if exists VIDEO_GAME cascade
116. Hibernate:
117.
118. create table VIDEO_GAME (
119. id varchar(36) not null,
120. GENRE varchar(255) not null,
121. NAME varchar(255) not null,
122. VERSION int8,
123. primary key (id)
124. )
125. Hibernate:
126.
127. alter table if exists VIDEO_GAME
128. add constraint UK_jg5tlrbpecd0wd8c9vjo6b429 unique (NAME)
129.
130. Hibernate:
131. INSERT INTO VIDEO_GAME(ID,NAME,GENRE,VERSION) VALUES ('896b9c77-4f6d-4bd6-
b681-2791acfa0d51','100 4 1','REFLEXION',1)
132. Hibernate:
133. INSERT INTO VIDEO_GAME(ID,NAME,GENRE,VERSION) VALUES ('6bf157fa-bd95-46ce-
bbca-58afb87ebb9b','10TH FRAME','SPORT',1)
134. Hibernate:
135. INSERT INTO VIDEO_GAME(ID,NAME,GENRE,VERSION) VALUES ('e603e430-0853-46b0-9f44-
d4f662f56c51','1943 : THE BATTLE OF MIDWAY','SHOOT_THEM_UP',1)
136. Hibernate:
137. INSERT INTO VIDEO_GAME(ID,NAME,GENRE,VERSION) VALUES ('61ec5869-
d9f5-497a-9ffc-8e3612892c4b','1ST DIVISION MANAGER','SPORT',1)
138.
139. ... etc ...
140. Hibernate:
141. select
142. videogame0_.id as id1_0_,
143. videogame0_.GENRE as genre2_0_,
144. videogame0_.NAME as name3_0_,
145. videogame0_.VERSION as version4_0_
146. from
147. VIDEO_GAME videogame0_ limit ?

- 29 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

148. [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.89 s - in
fr.fxjavadevblog.qjg.videogame.IT_VideoGameResource
149. [INFO]
150. [INFO] Results:
151. [INFO]
152. [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
153. [INFO]
154. [INFO]
155. [INFO] --- docker-maven-plugin:0.33.0:stop (docker:stop) @ quarkus-tuto ---
156. 19:29:29.455 PostgreSQL Server :2020-04-14 17:29:29.454 UTC [1] LOG: received smart
shutdown request
157. 19:29:29.460 PostgreSQL Server :2020-04-14 17:29:29.459 UTC [1] LOG: background
worker "logical replication launcher" (PID 71) exited with exit code 1
158. 19:29:29.460 PostgreSQL Server :2020-04-14 17:29:29.460 UTC [66] LOG: shutting down
159. 19:29:29.496 PostgreSQL Server :2020-04-14 17:29:29.496 UTC [1] LOG: database system is
shut down
160. [INFO] DOCKER> [postgres:12.2] "postgresql": Stop and removed container ca6a77a7d1e3 after 0
ms
161. [INFO]
162. [INFO] --- maven-failsafe-plugin:2.22.2:verify (default) @ quarkus-tuto ---
163. [INFO] ------------------------------------------------------------------------
164. [INFO] BUILD SUCCESS
165. [INFO] ------------------------------------------------------------------------

XIII - Version native et image Docker

Pour générer l'image Docker de l'application native, rien de plus facile que de lancer :

1. $ mvn clean package -Pnative -Dquarkus.native.container-build=true -Dskip.surefire.tests


2. ...
3. ...
4. [INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Pulling image quay.io/quarkus/
ubi-quarkus-native-image:19.3.1-java11
5. 19.3.1-java11: Pulling from quarkus/ubi-quarkus-native-image
6. 57de4da701b5: Pull complete
7. cf0f3ebe9f53: Pull complete
8. e9da77aa316d: Pull complete
9. Digest: sha256:b18b701bd6f9d0a7778129f63b9f2dd666be2a2574854b56cd60e3cbd42b73d3
10. Status: Downloaded newer image for quay.io/quarkus/ubi-quarkus-native-image:19.3.1-java11
11. quay.io/quarkus/ubi-quarkus-native-image:19.3.1-java11
12. [INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Running Quarkus native-image
plugin on GraalVM Version 19.3.1 CE
13. [INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] docker run -v /home/robin/
git/quarkus-tuto/target/quarkus-tuto-0.0.1-SNAPSHOT-native-image-source-jar:/project:z --
env LANG=C --user 1000:1000 --rm quay.io/quarkus/ubi-quarkus-native-image:19.3.1-java11 -J-
Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dsun.nio.ch.maxUpdateArraySize=100
-J-DCoordinatorEnvironmentBean.transactionStatusManagerEnable=false -J-Dvertx.logger-
delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory
-J-Dvertx.disableDnsResolver=true -J-Dio.netty.leakDetection.level=DISABLED -J-
Dio.netty.allocator.maxOrder=1 -J-Duser.language=fr -J-Dfile.encoding=UTF-8 --initialize-at-
build-time= -
H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime
-H:+JNI -jar quarkus-tuto-0.0.1-SNAPSHOT-runner.jar -H:FallbackThreshold=0
-H:+ReportExceptionStackTraces -H:-AddAllCharsets -H:-IncludeAllTimeZones -
H:EnableURLProtocols=http,https --enable-all-security-services --no-server -H:-
UseServiceLoaderFeature -H:+StackTrace quarkus-tuto-0.0.1-SNAPSHOT-runner
14. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] classlist: 10 740,32 ms
15. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] (cap): 793,01 ms
16. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] setup: 2 161,94 ms
17. 08:35:25,649 INFO [org.hib.Version] HHH000412: Hibernate ORM core version 5.4.12.Final
18. 08:35:25,663 INFO [org.hib.ann.com.Version] HCANN000001: Hibernate Commons Annotations
{5.1.0.Final}
19. 08:35:25,705 INFO [org.hib.dia.Dialect] HHH000400: Using dialect:
io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect
20. 08:35:25,848 INFO [org.hib.val.int.uti.Version] HV000001: Hibernate Validator 6.1.2.Final
21. 08:35:27,547 INFO [org.jbo.threads] JBoss Threads version 3.0.1.Final
22. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] (typeflow): 47 866,54 ms
23. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] (objects): 33 462,47 ms
24. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] (features): 1 692,11 ms

- 30 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

25. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] analysis: 87 791,76 ms


26. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] (clinit): 1 211,71 ms
27. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] universe: 5 195,59 ms
28. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] (parse): 6 258,47 ms
29. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] (inline): 8 432,95 ms
30. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] (compile): 55 066,12 ms
31. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] compile: 74 232,51 ms
32. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] image: 5 742,28 ms
33. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] write: 1 469,31 ms
34. [quarkus-tuto-0.0.1-SNAPSHOT-runner:24] [total]: 187 812,32 ms
35. [INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 223001ms
36. [INFO] ------------------------------------------------------------------------
37. [INFO] BUILD SUCCESS
38. [INFO] ------------------------------------------------------------------------
39. [INFO] Total time: 03:50 min
40. [INFO] Finished at: 2020-04-15T10:38:21+02:00
41. [INFO] ------------------------------------------------------------------------

Cette commande a réalisé une chose essentielle : elle a compilé toute l'application en une version native LINUX, quel
que soit votre environnement de travail, au moyen d'un conteneur dédié à la compilation.

Pour faire simple, un conteneur Docker ubi-quarkus-native-image:19.3.1-java11 a été récupéré puis lancé pour
compiler l'application au format LINUX, même si vous êtes sous Windows. Cela nécessite toutefois d'avoir GraalVM
installé nativement, mais cela peut-être contourné
(cf : https://quarkus.io/guides/building-native-image)

Une fois l'application compilée, il faut maintenant créer l'image du conteneur Docker. Cette création est possible en
ayant préalablement créé 2 fichiers dans /src/main/docker :

• /src/main/docker/Dockerfile.native : fichier pour la génération en mode natif ;


• .dockerignore : fichier pour la génération en mode JVM normal à la racine du projet Maven.

Il peut aussi exister un fichier Dockerfile.jvm mais ce n'est pas l'objet de ce tutoriel.

Voici le contenu de ces fichiers :

Dockerfile.native
1. FROM registry.access.redhat.com/ubi8/ubi-minimal
2. WORKDIR /work/
3. COPY target/*-runner /work/application
4. RUN chmod 775 /work
5. EXPOSE 8080
6. CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

.dockerignore
*
!target/*-runner
!target/*-runner.jar

On peut alors lancer la création de l'image Docker :

1. $ docker build -f src/main/docker/Dockerfile.native -t quarkus-tuto .


2. Sending build context to Docker daemon 102.9MB
3. Step 1/6 : FROM registry.access.redhat.com/ubi8/ubi-minimal
4. latest: Pulling from ubi8/ubi-minimal
5. b26afdf22be4: Pull complete
6. 218f593046ab: Pull complete
7. Digest: sha256:df6f9e5d689e4a0b295ff12abc6e2ae2932a1f3e479ae1124ab76cf40c3a8cdd
8. Status: Downloaded newer image for registry.access.redhat.com/ubi8/ubi-minimal:latest
9. ---> 91d23a64fdf2
10. Step 2/6 : WORKDIR /work/

- 31 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

11. ---> Running in 40a5ee141273


12. Removing intermediate container 40a5ee141273
13. ---> 6ab61dca9bf2
14. Step 3/6 : COPY target/*-runner /work/application
15. ---> 3aae05f5ee7d
16. Step 4/6 : RUN chmod 775 /work
17. ---> Running in 8387b2b6e071
18. Removing intermediate container 8387b2b6e071
19. ---> 0c03e70d6326
20. Step 5/6 : EXPOSE 8080
21. ---> Running in 1813737fd08e
22. Removing intermediate container 1813737fd08e
23. ---> 978251ac9f2b
24. Step 6/6 : CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
25. ---> Running in c93ab35754d4
26. Removing intermediate container c93ab35754d4
27. ---> 6009aee9381b
28. Successfully built 6009aee9381b
29. Successfully tagged quarkus-tuto:latest

Une fois le conteneur créé, il suffit de le lancer, en ayant lancé préalablement une instance de PostgreSQL (encore
avec Docker, comme en DEV) :

$ docker run -i --rm -p 8080:8080 --network="host" quarkus-tuto

Le paramètre --network="host" permet à l'application de se connecter au PostgreSQL exposé


par Docker.

1. $ curl http://localhost:8080/api/videogames/v1/genre/rpg
2. [
3. {
4. "id": "75a9b985-c5a9-40a0-87ba-086850683bfc",
5. "name": "20000 LIEUES SOUS LES MERS",
6. "genre": "rpg",
7. "version": 1
8. },
9. {
10. "id": "2b412dd7-d090-4328-8180-869b60bbc106",
11. "name": "ADVENTURE",
12. "genre": "rpg",
13. "version": 1
14. },
15. {
16. "id": "885a3475-63c9-49f3-b120-e218ce9c9510",
17. "name": "ADVENTURER, THE",
18. "genre": "rpg",
19. "version": 1
20. },
21. {
22. "id": "a500c1a6-6008-45d4-b3f4-5b668b77499a",
23. "name": "ADVENTURES OF ROBIN HOOD, THE",
24. "genre": "rpg",
25. "version": 1
26. },
27. ... etc ...
28. ]

XIV - Health Check et Metrics

Quarkus embarque les extensions SmallRye Health et Metrics, qui sont les implémentations respectives de Eclipse
MicroProfile Health et Metrics.

Le simple ajout dans le pom de ces deux dépendances rend ces fonctionnalités opérationnelles :

- 32 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

1. <!-- Health Check -->


2. <dependency>
3. <groupId>io.quarkus</groupId>
4. <artifactId>quarkus-smallrye-health</artifactId>
5. </dependency>
6. <!-- Metrics -->
7. <dependency>
8. <groupId>io.quarkus</groupId>
9. <artifactId>quarkus-smallrye-metrics</artifactId>
10. </dependency>

Une fois l'application lancée et qu'elle est sollicitée, on peut obtenir un état de son bon fonctionnement :

1. $ curl http://localhost:8080/health
2. {
3. "status": "UP",
4. "checks": [
5. {
6. "name": "Application",
7. "status": "UP"
8. },
9. {
10. "name": "Database connections health check",
11. "status": "UP"
12. }
13. ]
14. }

On peut obtenir quelques mesures qui auront été calculées au moyen de l'annotation @Timed sur les méthodes de
la classe VideoGameResource :

1. @Timed(name = "videogames-find-by-all", absolute = true,


2. description = "A measure of how long it takes to fetch all video games.",
3. unit = MetricUnits.MILLISECONDS)
4. public Iterable<VideoGame> findAll()
5. {
6. return videoGameRepository.findAll();
7. }
8. @Timed(name = "videogames-find-by-genre", absolute = true,
9. description = "A measure of how long it takes to fetch all video games filtered by a
given genre.",
10. unit = MetricUnits.MILLISECONDS)
11. public List<VideoGame> findByGenre(@PathParam("genre") Genre genre)
12. {
13. return videoGameRepository.findByGenre(genre);
14. }

L'attribut absolute=true empêche la concaténation du nom du package et de la classe au nom


de la mesure. Ceci sera plus agréable à lire dans les outils de restitution qui exploiteront cette
information du retour JSON. Je préfère cette notation, car elle aura un impact direct sur les
URL d'appel des mesures.

Voici les mesures obtenues après 20 appels de l'URL /api/videogames/v1/genre/shoot-them-up :

1. $ curl -H"Accept: application/json" localhost:8080/metrics/application/videogames-find-by-


genre
2. {
3. "videogames-find-by-genre": {
4. "p99": 10.044791,
5. "min": 2.335977,
6. "max": 10.044791,
7. "mean": 3.6114086322681627,
8. "p50": 3.19797,
9. "p999": 10.044791,

- 33 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/
Tutoriel pour réduire l'empreinte serveur d'une API REST en JAVA en la compilant en code natif avec Quarkus par François-Xavier Robin

10. "stddev": 1.6164326637605608,


11. "p95": 5.663952,
12. "p98": 10.044791,
13. "p75": 3.752217,
14. "fiveMinRate": 0.06305266722909629,
15. "fifteenMinRate": 0.021812705995763727,
16. "meanRate": 0.008589683736876096,
17. "count": 20,
18. "oneMinRate": 0.252757448780742
19. }
20. }

Il faut ensuite utiliser un collecteur de Metrics comme Prometheus couplé à Grafana pour
obtenir de jolis tableaux de bord.

XV - Conclusions

Quarkus est, à mon humble avis, un framework de développement de Web Services REST très intéressant sur de
nombreux aspects :

• il est facile à prendre en main ;


• le mode dev et le hot reload offrent un gain de temps important, même si l'usage conjoint de Lombok n'est
pas encore optimum ;
• la documentation est claire et il y a de nombreux exemples officiels sur GitHub ;
• la conformité aux specs Java EE et Microprofile est très intéressante et rassurante ( JAX-RS, etc.) : pas de
nouvelle API propriétaire à apprendre ;
• le plugin de compilation native avec GraalVM est fourni et le résultat est à la hauteur des espérances ;
• l'usage du Health Check et des Metrics est vraiment bien intégré et facile à mettre en œuvre ;
• il est facile de rajouter la gestion de token JWT et la liaison avec KeyCloak ;
• la communauté semble très active.

Je vous encourage donc vivement à vous pencher sérieusement sur Quarkus !

XVI - Remerciements

Cet article a été publié avec l'aimable autorisation François-Xavier Robin.

Nous tenons à remercier escartefigue pour sa relecture orthographique attentive de cet article et Winjerome pour
la mise au gabarit.

- 34 -
Copyright ® 2020 François-Xavier Robin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans
l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.
https://fxrobin.developpez.com/tutoriels/java/application-rest-quarkus-jpa-graalvm-docker/

Vous aimerez peut-être aussi