Tutoriel Vert.x 3 : créer, lancer et tester un verticle

Thierry-Leriche-Dessirier

Vert.x est une API asynchrone très proche du modèle d'acteurs. Vert.x est polyglotte, simple, scalable (élastique) et hautement concurrente. Vert.x est bien adapté aux architectures en microservices, mais s'intègre aussi parfaitement dans une WebApp plus classique. Dans ce premier article d'une série consacrée à Vert.x 3, nous allons tout d'abord voir comment installer le framework, écrire un premier verticle, le déployer et le tester. Commentez

- Titre : Tutoriel Vert.x 3 : créer, lancer et tester un verticle

- Auteur : Thierry-Leriche-Dessirier

- Parution : 19 octobre 2015

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2015 Thierry-Leriche-Dessirier. 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.

Sommaire

- I. Introduction

- I-A. Série d'articles

- I-B. Pourquoi Vert.x ?

- I-C. Versions

- II. Un projet Vert.x (avec Maven)

- III. Un premier verticle (Hello World)

- III-A. Les bases

- III-B. Futures

- III-C. Les routes

- IV. Lancer mon verticle

- IV-A. Avec Vert.x

- IV-B. Depuis Eclipse

- IV-C. Avec un fat-jar

- V. Déployer plusieurs verticles

- VI. Tester un verticle

- VII. Conclusions

- VIII. Remerciements

- IX. Annexes

- IX-A. Liens

- IX-B. Codes complets

- IX-C. Akka, Storm, NodeJs et les autres

- IX-C-1. Vert.x Versus NodeJS

- IX-C-2. Vert.x Versus Akka

- IX-C-3. Vert.x Versus Storm

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 :

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() » :

HelloWorld.java : 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 :

HelloWorld.java : création d'un serveur HTTP

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 :

HelloWorld.java : une réponse classique

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.

HelloWorld.java : avec des lambdas

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.

HelloWorld.java : avec gestion du résultat

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 :

pom.xml : vertx-web

<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>${vertx.version}</version>
</dependency>

On peut maintenant passer au code, qui est relativement simple.

HelloWorld.java : le routeur

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.

HelloWorld.java : utilisation du routeur par le serveur HTTP

vertx.createHttpServer()
.requestHandler(router::accept) // ici
.listen(8080);

Pour résumer, voici le code entier de la méthode.

HelloWorld.java : code entier

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

HelloWorld.java : plusieurs routes

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

Console : test de la version

C:\> vertx -version
3.0.0

On peut maintenant lancer le verticle Hello World qu'on a écrit plus tôt :

Console : lancement

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.

HelloWorld.java : avec des System.out

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

Console : les System.out en action

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 :

pom.xml : fat-jar

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

Console : package

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 :

Console : lancement du fat-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.

BlaBlaVerticle.java : un verticle simple qui ne fait rien

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

MyMainVerticle.java : un verticle pour orchestrer

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 :

pom.xml : le nouveau verticle principal

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

MyMainVerticle.java : déploiement de plusieurs 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 :

Console : package et run

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.

pom.xml : dépendances pour les tests

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

HelloWorldTest.java : une classe de test 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 ».

HelloWorldTest.java : préparation

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

HelloWorldTest.java : le test a proprement parlé

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

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

pom.xml

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

HelloWorldTest.java

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.