I. Introduction▲
Vert.x est un framework (sous licence Apache 2.0) destiné à la plate-forme Java qui a la particularité d'être polyglotte. Il a été conçu pour faciliter la création d'applications asynchrones et événementielles, tout en fournissant des performances (vitesse, mémoire, threads) excellentes. De par ses caractéristiques, Vert.x est notamment un bon choix pour l'écriture de microservices.
Pour un développeur JEE expérimenté, le passage à Vert.x nécessitera un petit changement de paradigme, mais ce sera relativement facile, car il sera guidé par l'API. Et d'ailleurs, ce qu'on peut en dire, c'est que Vert.x est :
- polyglotte : on peut écrire des composants dans plusieurs langages, dont Java, JavaScript, CoffeeScript, Ruby, Python, Groovy, Scala, etc. De manière générale, on peut utiliser tous les langages qui fonctionnent sur la JVM (certains sont encore en bêta dans Vert.x 3). En outre, on peut mélanger plusieurs de ces langages au sein d'une même application. C'est Vert.x qui se charge de tout ;
- simple : sans être simpliste, ce que vous allez découvrir à travers cette série d'articles, l'API est relativement simple à prendre en main. Vert.x permet d'écrire des applications non bloquantes sans nécessiter des compétences avancées (bon, ce n'est pas non plus pour débutant) ;
- élastique (scalable) : Vert.x utilise largement un système de messages, ce qui lui permet d'être plus efficace dans la gestion des cœurs ;
- concurrent : étant très proche d'un modèle d'acteurs et/ou microservice, l'API permet de se concentrer sur le métier, sans se préoccuper des contraintes et problèmes des architectures multithread.
Plus concrètement, ce qu'on peut en retenir, c'est que Vert.x est :
- polyglotte : chaque développeur peut utiliser le langage (et ses spécificités) qui lui semblera le plus adapté au contexte, et/ou avec lequel il est le plus à l'aise ;
- asynchrone : meilleure utilisation du CPU dans un contexte de forte parallélisation I/O ;
- un modèle d'acteurs en cluster (les verticles sont des acteurs qui s'échangent des messages au-dessus de l'event bus). Pour un parallèle avec le monde Scala, Vert.x offre un éventail de services qui correspond en gros à Spray + Akka.
Encore plus concrètement, Vert.x vous sera particulièrement utile pour développer une application très performante (rapide) sous une forte charge (avec beaucoup d'utilisateurs simultanés).
Vous trouverez les codes présentés dans cet article dans le fichier code.zip.
I-A. Série d'articles▲
Cet article fait partie d'une série consacrée à Vert.x 3 :
- créer et lancer un verticle ;
- Event bus (bientôt) ;
- bases de données (bientôt) ;
- Web services (bientôt) ;
- When-RX (bientôt) ;
- In-memory data grid (bientôt) ;
- sessions (bientôt) ;
- sécurité (bientôt).
I-B. Pourquoi Vert.x ?▲
À titre personnel, voici ce que j'en pense. Vert.x est une techno vraiment intéressante et relativement simple à prendre en main, au moins pour commencer. Passer de JEE à Vert.x impose toutefois un changement de paradigme, notamment sur la réactivité, l'asynchronisme ou encore les messages. On verra tout cela dans les prochains articles. Loin d'être insurmontable, ça ne sera tout de même pas évident. Et j'ajouterais que Vert.x est beaucoup moins facile d'accès que JEE, d'autant plus que de très nombreux framework et bibliothèques (comme Spring, Guava et tant d'autres) viennent simplifier le travail du développeur.
Alors, pourquoi choisir Vert.x, ou même migrer de JEE à Vert.x ? À mon sens, si vous êtes content de votre Tomcat, alors il n'y a pas de raison légitime de changer. Si, par contre, vous avez besoin de performances accrues, sur la concurrence, sur la vitesse, sur la charge mémoire (etc.) alors Vert.x va vous permettre d'aller (beaucoup) plus loin. Plus concrètement, quand votre conteneur JEE ne suffit plus, alors Vert.x est clairement l'une des options que vous devez examiner.
En outre, et au-delà de la question des performances, on peut se demander si on a toujours besoin de conteneur (JBoss, Tomcat, WebSphere, etc.) en 2015. Si le seul service que vous utilisez dans Tomcat est fourni par les servlets, ou si par exemple, vous développez principalement des web services (attention : Vert.x n'est pas dédié aux web services), alors Vert.x sera un choix excellent. Non seulement ce sera performant, mais, en plus, vous allez pouvoir supprimer Tomcat de la stack technique, ce qui est loin d'être négligeable.
Enfin, pourquoi Vert.x et non Akka, ou NodeJs, ou Storm, etc. ? Chacun de ces outils possède de particularités qui lui sont propres. Chacun a ses points forts et ses points faibles, ce que je vous propose de découvrir en annexe.
I-C. Versions▲
Pour écrire cet article, j'ai utilisé les logiciels suivants :
- Vert.x 3.0.0 ;
- Java JDK 1.8.0_45 ;
- Maven 3.2.5 ;
- Eclipse 4.5.
II. Un projet Vert.x (avec Maven)▲
Avant d'écrire notre premier programme utilisant Vert.x, je vous propose de mettre en place un projet Maven. Contrairement à la version 2 de Vert.x, la version 3 ne propose pas d'archétype. Je vous invite donc à placer le bout de code (code complet en annexe) suivant dans votre pom.xml :
<groupId>
com.masociete</groupId>
<artifactId>
tuto-vertx-01</artifactId>
<version>
1.0-SNAPSHOT</version>
<properties>
<vertx.version>
3.0.0</vertx.version>
<java.version>
1.8</java.version>
<maven-compiler-plugin.version>
3.3</maven-compiler-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>
io.vertx</groupId>
<artifactId>
vertx-core</artifactId>
<version>
${vertx.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
${maven-compiler-plugin.version}</version>
<configuration>
<source>
${java.version}</source>
<target>
${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
Compilez le projet d'exemple et importez-le dans Eclipse (comme expliqué dans le tutoriel« Importer un projet Maven dans Eclipse en 5 minutes ») ou dans l'IDE de votre choix.
C'est aussi simple que cela. Notez au passage que Java 8 est nécessaire pour faire fonctionner Vert.x 3.
III. Un premier verticle (Hello World)▲
III-A. Les bases▲
Vert.x est polyglotte. Cela veut dire qu'on peut programmer pour Vert.x dans plusieurs langages, dont Java, JavaScript, CDescriptif Ruby, Python, Groovy, etc. Dans cet article, nous nous limiterons toutefois à Java.
Écrites en Java, les applications Vert.x (dans la suite, je parlerai de « verticles ») doivent implémenter l'interface « Verticle » (on étendra la classe « AbstractVerticle ») et redéfinir la méthode « start() » :
import
io.vertx.core.AbstractVerticle;
public
class
HelloWorld extends
AbstractVerticle {
@Override
public
void
start
(
) throws
Exception {
...
}
}
La suite consiste à lancer un serveur HTTP. Disons, pour l'exemple, qu'il devra écouter sur le port 8080 :
import
io.vertx.core.Handler;
import
io.vertx.core.http.HttpServerRequest;
...
vertx.createHttpServer
(
)
.requestHandler
(
new
Handler<
HttpServerRequest>(
) {
@Override
public
void
handle
(
final
HttpServerRequest request) {
...
}
}
).listen
(
8080
);
Le handler qu'on passe en paramètre sera appelé à chaque fois qu'une requête sera reçue sur le port spécifié (ici 8080).
Venant du monde JEE, j'ai tendance à faire un parallèle entre les applications Vert.x, qui étendent « Verticle », et les servlets, qui étendent « HttpServlet ». Il faut toutefois se méfier de cette comparaison.
Et finalement, il ne reste plus qu'à coder le contenu de la réponse :
import
io.vertx.core.http.HttpServerResponse;
...
final
HttpServerResponse response =
request.response
(
);
response.headers
(
).set
(
"Content-Type"
, "text/plain"
);
response.write
(
"<http><body>"
);
response.write
(
"<h1>Hello World</h1>"
);
response.write
(
"</body></http>"
);
response.end
(
);
Je vous propose de résumer tout cela avec une lambda, en chaînant les appels.
public
class
HelloWorld extends
AbstractVerticle {
@Override
public
void
start
(
) throws
Exception {
vertx.createHttpServer
(
)
.requestHandler
(
req ->
req.response
(
)
.putHeader
(
"content-type"
, "text/html"
)
.end
(
"<html><body><h1>Hello World</h1></body></html>"
))
.listen
(
8080
);
}
}
Il y a un point sur lequel je voudrais insister. Définir une lambda et l'exécuter sont deux choses différentes. N'oubliez pas qu'une lambda est un objet. Dans l'exemple, la lambda …« req->… » correspond donc à un handler qu'on passe au serveur HTTP. La création de la lambda est quasi instantanée. Ce n'est que lorsque le serveur va recevoir une requête que la lambda (fonction) va être exécutée.
III-B. Futures▲
Quand on lance (déploie) un verticle, il démarre de manière asynchrone. Pour aller un peu plus loin, on peut passer un paramètre à la méthode « start() », ce qui permet de gérer les éventuelles exceptions.
import
io.vertx.core.Future;
...
@Override
public
void
start
(
final
Future<
Void>
startFuture) throws
Exception {
vertx.createHttpServer
(
)
.requestHandler
(
req ->
req.response
(
)
.putHeader
(
"content-type"
, "text/html"
)
.end
(
"<html><body><h1>Hello World</h1></body></html>"
))
.listen
(
8080
, res ->
{
if
(
res.succeeded
(
)) {
startFuture.complete
(
);
}
else
{
startFuture.fail
(
res.cause
(
));
}
}
);
}
Dans le cadre du Hello World, je vous accorde que l'intérêt est assez faible. Dans une « vraie » application, cela permet notamment de vérifier/attendre qu'un verticle se soit correctement déployé. On peut notamment réaliser un démarrage asynchrone du verticle et indiquer quand c'est fait à l'aide du futur. Nous utiliserons d'ailleurs cette technique dès le prochain épisode.
III-C. Les routes▲
C'est bien joli d'écouter sur le port 8080, mais on voudrait être un peu plus précis. Par exemple, au lieu d'écouter bêtement sur l'adresse « localhost:8080 », on voudrait que le verticle « Hello World » écoute sur « localhost:8080/hello ». Pour cela, le plus simple est d'écrire un routeur.
Pour commencer, vous devez ajouter une dépendance à votre pom.xml, car, jusque-là, nous n'avions que la partie « core » de Vert.x :
<dependency>
<groupId>
io.vertx</groupId>
<artifactId>
vertx-web</artifactId>
<version>
${vertx.version}</version>
</dependency>
On peut maintenant passer au code, qui est relativement simple.
final
Router router =
Router.router
(
vertx);
router.get
(
"/hello/"
)
.handler
(
req ->
req.response
(
)
.putHeader
(
"content-type"
, "text/html"
)
.end
(
"<html><body><h1>Hello World</h1></body></html>"
));
En fait, le contenu du handler du routeur est tout simplement un copié-collé de celui du serveur qu'on avait écrit plus tôt. Du coup, on peut utiliser le routeur dans le serveur.
vertx.createHttpServer
(
)
.requestHandler
(
router::accept) // ici
.listen
(
8080
);
Pour résumer, voici le code entier de la méthode.
@Override
public
void
start
(
) throws
Exception {
final
Router router =
Router.router
(
vertx);
router.get
(
"/hello/"
)
.handler
(
req ->
req.response
(
)
.putHeader
(
"content-type"
, "text/html"
)
.end
(
"<html><body><h1>Hello World</h1></body></html>"
));
vertx.createHttpServer
(
)
.requestHandler
(
router::accept)
.listen
(
8080
);
}
Et si on a besoin de plusieurs URL, il suffit de reproduire l'opération autant de fois que nécessaire…
@Override
public
void
start
(
) throws
Exception {
final
Router router =
Router.router
(
vertx);
router.get
(
"/hello/"
)
.handler
(
req ->
req.response
(
)
.putHeader
(
"content-type"
, "text/html"
)
.end
(
"<html><body><h1>Hello World</h1></body></html>"
));
router.get
(
"/byebye/"
)
.handler
(
req ->
req.response
(
)
.putHeader
(
"content-type"
, "text/html"
)
.end
(
"<html><body><h1>Bye Bye World</h1></body></html>"
));
vertx.createHttpServer
(
)
.requestHandler
(
router::accept)
.listen
(
8080
);
}
IV. Lancer mon verticle▲
Je vois plusieurs façons (liste non exhaustive) pour lancer un projet Vert.x :
- en lançant le serveur Vert.x, sur lequel on aura installé le projet ;
- en lançant un « run » Eclipse ;
- en lançant le Vert.x embarqué dans le projet (fat-jar).
La première solution est évidemment celle que vous allez utiliser en Prod. Je vous recommande d'ailleurs de l'associer à Docker. Personnellement, pour des raisons pratiques évidentes, je ne l'utilise pas en Dev. Les deuxième et troisième solutions sont bien adaptées pour le Dev.
IV-A. Avec Vert.x▲
Pour installer le serveur Vert.x, il suffit d'en télécharger l'archive sur le site officiel de Vert.x. Si vous ne savez pas quelle version choisir, prenez carrément la version complète (« full »). Il faut ensuite en ajouter le dossier « bin » dans votre path.
C:\> vertx -version
3
.0
.0
On peut maintenant lancer le verticle Hello World qu'on a écrit plus tôt :
C:\> cd C:\...\com\masociete\tutovertx
C:\...\com\masociete\tutovertx>
vertx run HelloWorld.java
Succeeded in
deploying verticle
Vous remarquez que j'ai lancé un « .java » et non un « .class ».
On peut ensuite vérifier que ça fonctionne depuis un navigateur, ou un telnet, ou un curl, ou un Postman, etc. Le verticle écoute sur les adresses « localhost:8080/hello » et « localhost:8080/byebye » puisque ce sont les routes qu'on a configurées plut tôt.
Pour arrêter le verticle, il suffit de tuer le processus : Ctrl + C.
Pour bien comprendre comment le verticle fonctionne, et même si c'est moche, je vous propose d'ajouter des « System.out » dans le code.
@Override
public
void
start
(
) throws
Exception {
System.out.println
(
"Start of Hello World"
);
final
Router router =
Router.router
(
vertx);
router.get
(
"/hello/"
)
.handler
(
req ->
{
System.out.println
(
"Hello World"
);
req.response
(
)
.putHeader
(
"content-type"
, "text/html"
)
.end
(
"<html><body><h1>Hello World</h1></body></html>"
);
}
);
vertx.createHttpServer
(
) //
.requestHandler
(
router::accept) //
.listen
(
8080
);
Relancez le verticle et rechargez la page plusieurs fois.
C:\...\com\masociete\tutovertx>
vertx run HelloWorld.java
Start of Hello World
Succeeded in
deploying verticle
Hello World
Hello World
Hello World
IV-B. Depuis Eclipse▲
Lancer le verticle depuis Eclipse est assez pratique puisque vous n'aurez même pas à sortir de l'IDE. Pour cela, vous devez créer un nouveau « run » à l'aide du menu « Run/Run configurations ». Ajoutez une configuration « Java Application » et remplissez les champs comme suit (vous pouvez vous aider des captures d'écran) :
- Name : indiquez ce que vous voulez (chez moi « Verticle Hello World »") ;
- Project : choisissez (bouton « Browse ») votre projet (chez moi « tuto-vertx-01 ») ;
- Main class : indiquez « io.vertx.core.Starter » ;
- Program arguments : indiquez « run monpackage.MaClasse » où « monpackage.MaClasse » doit être remplacé par le nom de votre verticle (chez moi « com.masociete.tutovertx.HelloWorld »).
On peut également utiliser la variable « ${java_type_name} ».
Il ne reste plus qu'à lancer…
IV-C. Avec un fat-jar▲
Pour créer un « fat-jar », il suffit d'ajouter le plugin « shade » dans le pom.xml comme suit :
<properties>
<maven-shade-plugin.version>
2.3</maven-shade-plugin.version>
...
</properties>
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-shade-plugin</artifactId>
<version>
${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>
package</phase>
<goals>
<goal>
shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation
=
"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"
>
<manifestEntries>
<Main-Class>
io.vertx.core.Starter</Main-Class>
<Main-Verticle>
com.masociete.tutovertx.HelloWorld</Main-Verticle>
</manifestEntries>
</transformer>
</transformers>
<artifactSet />
<outputFile>
${project.build.directory}/${project.artifactId}-${project.version}-fat.jar</outputFile>
</configuration>
</execution>
</executions>
</plugin>
Dans cette configuration, j'indique la classe « HelloWorld » comme verticle principal. Dans une « vraie » application, on aurait plutôt lancé un verticle qui, lui, aurait déployé les autres verticles (cf. chapitre suivant).
Et il suffit de lancer la tâche « package » pour avoir notre fichier.
C:\...>
mvn clean package
[INFO] Scanning for
projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building tuto-vertx-01
1
.0
-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2
.5
:clean (
default-clean) @ tuto-vertx-01
---
[INFO] Deleting C:\...\target
[INFO]
[INFO] --- maven-resources-plugin:2
.6
:resources (
default-resources) @ tuto-vertx-01
---
[WARNING] Using platform encoding (
Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0
resource
[INFO]
[INFO] --- maven-compiler-plugin:3
.3
:compile (
default-compile) @ tuto-vertx-01
---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 2
source files to C:\...\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2
.6
:testResources (
default-testResources) @ tuto-vertx-01
---
[WARNING] Using platform encoding (
Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0
resource
[INFO]
[INFO] --- maven-compiler-plugin:3
.3
:testCompile (
default-testCompile) @ tuto-vertx-01
---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2
.12
.4
:test (
default-test) @ tuto-vertx-01
---
[INFO]
[INFO] --- maven-jar-plugin:2
.4
:jar (
default-jar) @ tuto-vertx-01
---
[INFO] Building jar: C:\...\target\tuto-vertx-01
-1
.0
-SNAPSHOT.jar
[INFO]
[INFO] --- maven-shade-plugin:2
.3
:shade (
default) @ tuto-vertx-01
---
[WARNING] Map in
class org.apache.maven.plugins.shade.resource.ManifestResourceTransformer declares value type as: class java.util.jar.Attribu
java.lang.String at runtime
[WARNING] Map in
class org.apache.maven.plugins.shade.resource.ManifestResourceTransformer declares value type as: class java.util.jar.Attribu
java.lang.String at runtime
[INFO] Including io.vertx:vertx-core:jar:3
.0
.0
in
the shaded jar.
[INFO] Including io.netty:netty-common:jar:4
.0
.28
.Final in
the shaded jar.
[INFO] Including io.netty:netty-buffer:jar:4
.0
.28
.Final in
the shaded jar.
[INFO] Including io.netty:netty-transport:jar:4
.0
.28
.Final in
the shaded jar.
[INFO] Including io.netty:netty-handler:jar:4
.0
.28
.Final in
the shaded jar.
[INFO] Including io.netty:netty-codec:jar:4
.0
.28
.Final in
the shaded jar.
[INFO] Including io.netty:netty-codec-http:jar:4
.0
.28
.Final in
the shaded jar.
[INFO] Including com.fasterxml.jackson.core:jackson-core:jar:2
.5
.3
in
the shaded jar.
[INFO] Including com.fasterxml.jackson.core:jackson-databind:jar:2
.5
.3
in
the shaded jar.
[INFO] Including com.fasterxml.jackson.core:jackson-annotations:jar:2
.5
.0
in
the shaded jar.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Et enfin, on lance le jar :
C:\...>
java -jar target\tuto-vertx-01
-1
.0
-SNAPSHOT-fat.jar
Start of Hello World
08
. 01
, 2015
00
:09
:41
PM io.vertx.core.Starter
INFOS: Succeeded in
deploying verticle
V. Déployer plusieurs verticles▲
Bien entendu, vous n'allez pas développer un seul verticle. Vous allez en développer plusieurs qui vont, ou non, interagir. Il est tout à fait possible de lancer chaque verticle séparément en reproduisant la procédure qu'on a utilisée plus tôt pour chacun des verticles. Dans la suite, on va considérer que nos verticles forment un tout et on va les lancer d'un seul coup.
À titre d'illustration, je vous propose d'ajouter un verticle « BlaBlaVerticle », qui ne fait qu'écrire dans la console.
public
class
BlaBlaVerticle extends
AbstractVerticle {
@Override
public
void
start
(
) throws
Exception {
System.out.println
(
"Start of Bla bla bla..."
);
}
}
On va aussi créer le verticle « MyMainVerticle » dont l'un des objectifs sera de lancer « Hello World » et « Bla Bla ».
public
class
MyMainVerticle extends
AbstractVerticle {
@Override
public
void
start
(
) throws
Exception {
System.out.println
(
"Start of My Main Verticle"
);
}
}
Pour l'instant, ce verticle ne fait rien. Patience, ça va venir. N'oubliez pas de faire le changement dans le fichier pom.xml :
<Main-Verticle>
com.masociete.tutovertx.MyMainVerticle</Main-Verticle>
Enfin, on peut indiquer au verticle « My Main Verticle » de lancer (déployer) les deux autres verticles :
@Override
public
void
start
(
) throws
Exception {
System.out.println
(
"Start of My Main Verticle"
);
vertx.deployVerticle
(
"com.masociete.tutovertx.HelloWorld"
);
vertx.deployVerticle
(
"com.masociete.tutovertx.BlaBlaVerticle"
);
}
Et quand on refait le package et qu'on lance le verticle :
C:\...>
mvn clean package
...
C:\...>
java -jar target\tuto-vertx-01
-1
.0
-SNAPSHOT-fat.jar
Start of My Main Verticle
Start of Hello World
Start of Bla bla bla...
08
. 01
, 2015
00
:30
:41
PM io.vertx.core.Starter
INFOS: Succeeded in
deploying verticle
On peut aussi synchroniser le lancement des différents verticles, mais je vous épargne ça dans ce premier article.
VI. Tester un verticle▲
Maintenant qu'on est content avec tout ça et que ça a l'air de fonctionner correctement. Je vous propose d'aller à l'étape suivante, qui consiste à écrire des tests qui nous permettront de vérifier que ça marche pour de vrai.
En TDD/3T, on aurait commencé par les tests, mais ça aurait été un peu violent dans un article d'introduction.
Sans surprise, on va utiliser JUnit, qui nécessite d'ajouter une dépendance dans le pom.xml. On va aussi en profiter pour ajouter une dépendance spécialisée dans les tests pour Vert.x.
<properties>
<junit.version>
4.12</junit.version>
...
</properties>
<dependencies>
<dependency>
<groupId>
junit</groupId>
<artifactId>
junit</artifactId>
<version>
${junit.version}</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
io.vertx</groupId>
<artifactId>
vertx-unit</artifactId>
<version>
${vertx.version}</version>
<scope>
test</scope>
</dependency>
</dependencies>
Pour la classe de test, au moins au début, on va rester dans le classique.
import
org.junit.runner.RunWith;
import
io.vertx.ext.unit.junit.VertxUnitRunner;
@RunWith
(
VertxUnitRunner.class
)
public
class
HelloWorldTest {
}
On va bien entendu avoir besoin de l'objet « Vertx » qu'on a déjà très largement utilisé jusque-là. Et comme vous l'avez sûrement compris, c'est un objet central dans le framework. On va s'en servir pour tout initialiser et fermer dans le « before » et le « after ».
import
org.junit.After;
import
org.junit.Before;
import
io.vertx.core.Vertx;
import
io.vertx.ext.unit.TestContext;
...
private
Vertx vertx;
@Before
public
void
before
(
final
TestContext context) {
vertx =
Vertx.vertx
(
);
vertx.deployVerticle
(
HelloWorld.class
.getName
(
), context.asyncAssertSuccess
(
));
}
@After
public
void
after
(
final
TestContext context) {
vertx.close
(
context.asyncAssertSuccess
(
));
}
Vous reconnaissez la méthode « deployVerticle() » qu'on avait déjà utilisée un peu plus tôt. Vous remarquez qu'on lui a passé un appel à « context.asyncAssertSuccess() » qui sort en erreur (test failed) lorsque le verticle ne s'est pas correctement déployé. C'est bien entendu directement lié aux « startFuture.complete() » et « startFuture.fail(..) » qu'on avait vus un peu plus tôt. Ici, en plus de vérifier que ça se passe bien, ça va aussi attendre que le verticle ait fini de se déployer. Quant à l'utilisation dans le « close() », vous avez deviné que ça fonctionne sur le même principe.
Enfin, on peut coder la méthode de test. On va juste appeler l'adresse sur laquelle le verticle écoute, puis s'assurer que la réponse contient bien le message « Hello World ».
import
io.vertx.ext.unit.Async;
...
private
final
static
int
PORT =
8080
;
private
final
static
String HOST =
"localhost"
;
private
final
static
String PATH_HELLO =
"/hello/"
;
private
final
static
String PATH_BYEBYE =
"/byebye/"
;
private
final
static
String EXPECTED_MESSAGE_HELLO =
"Hello World"
;
private
final
static
String EXPECTED_MESSAGE_BYEBYE =
"Bye Bye World"
;
@Test
public
void
testHelloWorld
(
final
TestContext context) {
final
Async async =
context.async
(
);
vertx.createHttpClient
(
)
.getNow
(
PORT, HOST, PATH_HELLO, response ->
{
response.handler
(
body ->
{
context.assertTrue
(
body.toString
(
).contains
(
EXPECTED_MESSAGE_HELLO));
async.complete
(
);
}
);
}
);
}
@Test
public
void
testByeByeWorld
(
final
TestContext context) {
final
Async async =
context.async
(
);
vertx.createHttpClient
(
)
.getNow
(
PORT, HOST, PATH_BYEBYE, response ->
{
response.handler
(
body ->
{
context.assertTrue
(
body.toString
(
).contains
(
EXPECTED_MESSAGE_BYEBYE));
async.complete
(
);
}
);
}
);
}
Dans un test comme celui-ci, pensez bien à utiliser « context.assertTrue(..) » et non le « Assert.assertTrue(..) » classique de JUnit. N'oubliez pas que Vert.x est un framework asynchrone…
Lancez les tests, soit depuis Eclipse, soit à l'aide de la commande « mvn clean test ». Les deux tests devraient être verts. C'est aussi simple que ça…
VII. Conclusions▲
On vient de voir qu'il est relativement simple de mettre en place un projet Vert.x dans sa version 3. Pour le moment, nous n'avons écrit qu'un simple Hello World. Même si cela laisse entrevoir le potentiel de Vert.x, sur différents aspects, il nous reste encore beaucoup de choses à étudier, à commencer par l'event bus qu'on pourrait considérer comme la colonne vertébrale de Vert.x. C'est ce que je vous propose comme sujet du prochain article de cette série.
Un dernier petit mot pour conclure sur Vert.x, je dirais qu'en raison de ses caractéristiques (on en verra un peu plus dans les prochains articles), Vert.x répond très bien aux besoins/contraintes qu'on pourrait rencontrer lors de la mise en place d'une architecture à base de microservices. Je vous laisse lire un billet de Martin Fowler à propos de ces derniers. Attention toutefois à ne pas vous imaginer que Vert.x ne sert qu'à écrire des web services. Vert.x est bien plus que ça, et nous le verrons dans les prochains articles…
Vos retours nous aident à améliorer nos publications. N'hésitez donc pas à commenter cet article sur le forum. 33 commentaires
VIII. Remerciements▲
D'abord, j'adresse mes remerciements à l'équipe Vert.x et aux créateurs de modules complémentaires, pour avoir développé une bibliothèque aussi utile et pour la maintenir. Je n'oublie pas tous les contributeurs qui participent, notamment sur le forum.
Plus spécifiquement en ce qui concerne cet article, je tiens à remercier l'équipe de Developpez.com et plus particulièrement à Logan Mauzaize, Mickael Baron, CyaNnOrangehead et Zoom61.
IX. Annexes▲
IX-A. Liens▲
Vert.x : http://vertx.io/
Mes autres articles : https://thierry-leriche-dessirier.developpez.com/
IX-B. Codes complets▲
<project
xmlns
=
"http://maven.apache.org/POM/4.0.0"
xmlns
:
xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi
:
schemaLocation
=
"http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.masociete</groupId>
<artifactId>
tuto-vertx-01</artifactId>
<version>
1.0-SNAPSHOT</version>
<properties>
<vertx.version>
3.0.0</vertx.version>
<java.version>
1.8</java.version>
<maven-compiler-plugin.version>
3.3</maven-compiler-plugin.version>
<maven-shade-plugin.version>
2.3</maven-shade-plugin.version>
<junit.version>
4.12</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>
io.vertx</groupId>
<artifactId>
vertx-core</artifactId>
<version>
${vertx.version}</version>
</dependency>
<dependency>
<groupId>
io.vertx</groupId>
<artifactId>
vertx-web</artifactId>
<version>
${vertx.version}</version>
</dependency>
<dependency>
<groupId>
junit</groupId>
<artifactId>
junit</artifactId>
<version>
${junit.version}</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
io.vertx</groupId>
<artifactId>
vertx-unit</artifactId>
<version>
${vertx.version}</version>
<scope>
test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
${maven-compiler-plugin.version}</version>
<configuration>
<source>
${java.version}</source>
<target>
${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-shade-plugin</artifactId>
<version>
${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>
package</phase>
<goals>
<goal>
shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation
=
"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"
>
<manifestEntries>
<Main-Class>
io.vertx.core.Starter</Main-Class>
<Main-Verticle>
com.masociete.tutovertx.MyMainVerticle</Main-Verticle>
</manifestEntries>
</transformer>
</transformers>
<artifactSet />
<outputFile>
${project.build.directory}/${project.artifactId}-${project.version}-fat.jar</outputFile>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
package
com.masociete.tutovertx;
import
io.vertx.core.AbstractVerticle;
import
io.vertx.ext.web.Router;
public
class
HelloWorld extends
AbstractVerticle {
@Override
public
void
start
(
) throws
Exception {
System.out.println
(
"Start of Hello World"
);
final
Router router =
Router.router
(
vertx);
router.get
(
"/hello/"
)
.handler
(
req ->
{
System.out.println
(
"Hello World"
);
req.response
(
)
.putHeader
(
"content-type"
, "text/html"
)
.end
(
"<html><body><h1>Hello World</h1></body></html>"
);
}
);
router.get
(
"/byebye/"
)
.handler
(
req ->
{
System.out.println
(
"Bye Bye World"
);
req.response
(
)
.putHeader
(
"content-type"
, "text/html"
)
.end
(
"<html><body><h1>Bye Bye World</h1></body></html>"
);
}
);
vertx.createHttpServer
(
)
.requestHandler
(
router::accept)
.listen
(
8080
);
}
}
package
com.masociete.tutovertx;
import
org.junit.After;
import
org.junit.Before;
import
org.junit.Test;
import
org.junit.runner.RunWith;
import
io.vertx.core.Vertx;
import
io.vertx.ext.unit.junit.VertxUnitRunner;
import
io.vertx.ext.unit.Async;
import
io.vertx.ext.unit.TestContext;
@RunWith
(
VertxUnitRunner.class
)
public
class
HelloWorldTest {
private
Vertx vertx;
private
final
static
int
PORT =
8080
;
private
final
static
String HOST =
"localhost"
;
private
final
static
String PATH_HELLO =
"/hello/"
;
private
final
static
String PATH_BYEBYE =
"/byebye/"
;
private
final
static
String EXPECTED_MESSAGE_HELLO =
"Hello World"
;
private
final
static
String EXPECTED_MESSAGE_BYEBYE =
"Bye Bye World"
;
@Before
public
void
before
(
final
TestContext context) {
vertx =
Vertx.vertx
(
);
vertx.deployVerticle
(
HelloWorld.class
.getName
(
), context.asyncAssertSuccess
(
));
}
@After
public
void
after
(
final
TestContext context) {
vertx.close
(
context.asyncAssertSuccess
(
));
}
@Test
public
void
testHelloWorld
(
final
TestContext context) {
final
Async async =
context.async
(
);
vertx.createHttpClient
(
)
.getNow
(
PORT, HOST, PATH_HELLO, response ->
{
response.handler
(
body ->
{
context.assertTrue
(
body.toString
(
).contains
(
EXPECTED_MESSAGE_HELLO));
async.complete
(
);
}
);
}
);
}
@Test
public
void
testByeByeWorld
(
final
TestContext context) {
final
Async async =
context.async
(
);
vertx.createHttpClient
(
)
.getNow
(
PORT, HOST, PATH_BYEBYE, response ->
{
response.handler
(
body ->
{
context.assertTrue
(
body.toString
(
).contains
(
EXPECTED_MESSAGE_BYEBYE));
async.complete
(
);
}
);
}
);
}
}
IX-C. Akka, Storm, NodeJs et les autres▲
Il existe plusieurs frameworks dont le principe de fonctionnement et les objectifs sont proches de ceux de Vert.x. Chacun d'eux possède des caractéristiques propres.
IX-C-1. Vert.x Versus NodeJS▲
La principale différence qui saute immédiatement aux yeux est que NodeJS fonctionne en JavaScript, tandis que Vert.x fonctionne en Java. Il est vrai, toutefois, que Vert.x est polyglotte et qu'il sait faire fonctionner du JavaScript, mais j'ai le sentiment que ce n'est pas vraiment sa raison d'être.
Si vous connaissez déjà NodeJS, vous ne serez pas perdu avec Vert.x puisque les deux frameworks partagent un grand nombre de caractéristiques. Je dirais que le modèle de NodeJS se rapproche surtout de celui de Vert.x 2, puisque Vert.x 3 a changé un peu de stratégie. Et la réciproque sera vraie ; si vous connaissez Vert.x, vous serez à l'aise rapidement avec NodeJS.
Les principales différences entre NodeJS et Vert.x sont liées aux différences entre JavaScript et Java. La plus importante est certainement le modèle monothread de JavaScript, versus le modèle multithread de Vert.x.
On ne le dira jamais assez, Java et JavaScript, bien qu'ils portent le même nom, bien qu'ils se ressemblent, sont deux langages très différents. Si, comme moi, vous êtes un développeur Java, vous allez forcément vous faire avoir à un moment ou à un autre à cause d'une des différences comme la portée des variables, les callback, le monothread, etc. On ne s'improvise pas si facilement développeur… Bref… Mon point est le suivant. NodeJS et Vert.x se battent à coup de benchs, mais l'un et l'autre se valent bien. Certains détails vous feront certainement pencher vers l'un ou l'autre. Parmi ceux-ci, celui qui me semble le plus important est la composition de votre équipe. Si votre équipe est composée d'experts JavaScript, alors prenez NodeJS. Si votre équipe est composée d'experts Java, alors prenez Vert.x, tout simplement.
On pourrait ajouter qu'il existe de très nombreux excellents modules pour NodeJS, ce qui représente un avantage certain.
IX-C-2. Vert.x Versus Akka▲
Akka est peut-être un peu plus connu que Vert.x. D'ailleurs NodeJS est également plus connu que Vert.x. Et autant, il est relativement simple de trouver des grosses différences entre Vert.x et NodeJS, autant cela va être beaucoup plus subtil avec Akka. Akka et Vert.x sont, tous les deux, conçus pour la plate-forme Java. Akka est clairement un modèle d'acteurs, ce qui est moins mis en avant avec Vert.x. Si vous avez besoin d'un modèle d'acteurs, prenez Akka ; il vous le rendra bien. Si vous avez surtout besoin de performance au niveau des IO ou d'un grid computing, Vert.x sera sûrement plus adapté.
D'un point de vue langage, mis à part Java qui est commun aux deux bien évidemment, Akka est très copain avec Scala. Si vous êtes adepte de ce langage, prenez Akka. Si vous préférez Java 8, JS, Python, Groovy (et bien d'autres), alors, jetez un coup d'œil à Vert.x.
Il est tout à fait possible de combiner les atouts de Vert.x et d'Akka au sein d'un même projet.
IX-C-3. Vert.x Versus Storm▲
Storm est assez agréable à utiliser. À mon sens, il se situe à mi-chemin entre Vert.x et Akka. Storm utilise des objets nommés « Spout » et « Bolt » qui ressemblent beaucoup aux verticles de Vert.x. Les Spouts sont plus spécifiquement dédiés à la lecture de données, ce qui se rapproche un peu des workers de Vert.x.
Une grosse différence entre Vert.x et Storm est relative au mode d'interaction entre les objets. Les verticles de Vert.x discutent via l'event bus et réagissent selon les événements. Les bolts/spouts de Storm s'enchaînent selon une configuration.