Jose Hernández

Vert.x

Vert.x es un toolkit para desarrollar aplicaciones reactivas en la JVM. Como sus propios creadores lo definen, Vert.x no es ni un servidor de aplicaciones, ni un framework, son simplemente componentes que pueden añadirse a una aplicación sin la necesidad de modificar la estructura de la misma.

Una aplicación reactiva es aquella que cumple con el manifiesto reactivo. El manifiesto reactivo indica que una aplicación reactiva debe cumplir los siguientes 4 puntos:

  • Responsive: tiene que responder a peticiones en un tiempo razonable.
  • Resilient: tiene que seguir funcionando en caso de errores, es decir, la aplicación tiene que estar diseñada para tratar con errores y seguir funcionando correctamente aunque ocurran.
  • Elastic: el sistema tiene que seguir siendo responsive aunque esté soportando altas carga, lo que implica que tiene que estar preparado para poder escalar.
  • Message driven: los componentes de un sistema reactivo tienen que interactuar mediante mensajes asíncronos, tanto entre ellos como entre componentes de terceros.

Vert.x está dirigido por eventos y es no bloqueante. Los eventos se gestionan mediante el Event Loop el cual se encarga de que cada uno de ellos llegue a su manejador correspondiente. Una regla de oro en Vert.x es que el Event Loop nunca tiene que ser bloqueado, ya que en ese caso no se enviarian eventos y la aplicación dejará de responder.

Otra característica importante de Vert.x es que se pueden usar una gran variedad de lenguajes para programar con él, Java, Groovy, Ceylon, Ruby e incluso JavaScript. Lo cual permite tener diversos equipos trabajando en distintos lenguajes pero con una base común.

Finalmente no podemos dejar de mencionar el Event Bus, un mecanismo ligero con el cual podemos enviar y recibir mensajes.

Servidor web con Vert.x

Una vez hechas las presentaciones vamos a ver un primer ejemplo de aplicación. Esta aplicación va a ser un simple servidor web que responderá a todas las peticiones con la frase Hello World!. Comencemos creando la clase HelloWorldServer que contendrá lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
package xyz.josehernandez.server;

import io.vertx.core.Vertx;

public class HelloWorldServer {
    public static void main(String args[]) {
        Vertx.vertx().createHttpServer()
            .requestHandler(req -> {
                req.response().end("Hello World!");
            })
            .listen(8080);
    }
}

Como se puede ver, en unas pocas lineas hemos montado un servidor completamente asíncrono utilizando Vert.x. Con el objeto vertx se ha creado un servidor HTTP usando la función createHttpServer. A continuación, al objeto HttpServer que devuelve esta función, se le ha añadido un requestHandler para gestionar las peticiones de los usuarios y finalmente se ha puesto a escuchar en el puerto 8080 con la llamada a listen.

El parámetro que recibe la función requestHandler es de tipo HttpServerRequest y es el código que se ejecuta cada vez que se recibe una petición. Desde el propio objeto HttpServerRequest que es el que recibimos en la lambda, llamamos al método response para obtener un objeto HttpServerResponse y sobre el que ejecutamos su método end para responder la petición. Es muy importante la llamada al método end, ya que hasta que no se le llama, la respuesta no se le envía al cliente.

Para generar un fichero jar y poder ejecutar la aplicación vamos a utilizar maven, para ello crearemos el fichero pom.xml con el siguiente contenido:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?xml version="1.0" encoding="UTF-8"?>
<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>xyz.josehernandez</groupId>
    <artifactId>hello-world-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
            <version>3.3.3</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</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>
                                            xyz.josehernandez.server.HelloWorldServer
                                        </Main-Class>
                                    </manifestEntries>
                                </transformer>
                                <transformer implementation="org.apache.maven.plugins.
                                        shade.resource.AppendingTransformer">
                                    <resource>
                                        META-INF/services/io.vertx.core.spi.VerticleFactory
                                    </resource>
                                </transformer>
                            </transformers>
                            <artifactSet></artifactSet>
                            <outputFile>
                                ${project.build.directory}/${project.artifactId}-
                                    ${project.version}-fat.jar
                            </outputFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>
</project>

Lo más interesante del fichero son la secciones dependencies y build. En dependencies indicamos que queremos usar la versión 3.3.3 del core de Vert.x. Mientras que en build con el primer componente plugin indicamos que la versión target y la de los fuentes de Java es la 1.8. Mientras que con la segunda componente indicamos que queremos usar el maven-shade-plugin para la generación del paquete .jar. En esta última sección hay que tener en cuenta que en la primera componente de transformer se indica cual es la clase donde se encuentra el main de nuestra aplicación.

Con todo esto claro, ahora abrimos un terminal y ejecutamos

> mvn clean package

Esto habrá creado un nuevo directorio llamado target al cual accedemos y veremos entre otros el fichero hello-world-server-1.0-SNAPSHOT-fat.jar. Para ejecutar este fichero, de nuevo en un terminal ejecutaremos:

> java -jar hello-world-server-1.0-SNAPSHOT-fat.jar

Si ahora abrimos un navegador y visitamos http://localhost:8080 podemos ver como nos responde Hello World!.

Para ver el código fuente completo puedes acceder aquí

Cliente web en Vert.x

Si montar el servidor web ha sido sencillo, montar un cliente también es una tarea muy sencilla como veremos a continuación. En esta ocasión crearemos una clase llamada HelloWorldClient y su contenido será el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package xyz.josehernandez.client;

import io.vertx.core.Vertx;

public class HelloWorldClient {
    public static void main(String args[]) {        
        Vertx.vertx().createHttpClient()
            .getNow(8080, "localhost", "/", res -> {
                res.bodyHandler(buffer -> {
                    System.out.println(buffer);
                });
            });
    }
}

De nuevo hemos necesitado pocas lineas para crear un cliente. se ha vuelto a usar el objeto vertx llamando esta vez a la función createHttpClient para obtener un objeto del tipo HttpClient con el que poder hacer peticiones. A continuación, con getNow se ha realizado una petición GET al puerto 8080 del servidor localhost y a la ruta /. El cuarto parámetro es una función que recibe un tipo HttpClientResponse al que con la función bodyHandler le añadimos un manejador para obtener la respuesta del sevidor que posteriormente mostramos por pantalla.

En este momento nos queda configurar el fichero pom.xml de este nuevo proyecto que será muy similar al del anterior, pero cambiaremos lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    ...
    
    <groupId>xyz.josehernandez</groupId>
    <artifactId>hello-world-client</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    ...
    
    <build>
        <plugins>
        ...
        
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</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>
                                            xyz.josehernandez.client.HelloWorldClient
                                        </Main-Class>
                                    </manifestEntries>
                                </transformer>
                                <transformer implementation="org.apache.maven.plugins.
                                        shade.resource.AppendingTransformer">
                                    <resource>
                                        META-INF/services/io.vertx.core.spi.VerticleFactory
                                    </resource>
                                </transformer>
                            </transformers>
                            <artifactSet></artifactSet>
                            <outputFile>
                                ${project.build.directory}/${project.artifactId}-
                                    ${project.version}-fat.jar
                            </outputFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Hemos modificado el nombre del artifactId a hello-world-client y en la sección del plugin maven-shade-plugin hemos indicado que la Main-Class es HelloWorldClient. Para comprobar su funcionamiento, ejecutaremos préviamente el ejemplo anterior:

> java -jar hello-world-server-1.0-SNAPSHOT-fat.jar

A continuación, abriremos un nuevo terminal e iremos a la localización de el proyecto cliente donde crearemos el fichero .jar con el comando:

> mvn clean package

Finalmente navegaremos hasta el directorio target dentro del proyecto y ejecutaremos el comando:

> java -jar hello-world-client-1.0-SNAPSHOT-fat.jar

El resultado será que el cliente le realiza la petición al servidor y cuando la obtiene, muestra por pantalla la frase: Hello World!

Para ver el código fuente completo puedes acceder aquí

Verticles

Las verticles son la forma de estructurar las aplicaciones en Vert.x. Como hemos visto con los ejemplos anteriores, no son requeridas para crear una aplicación, pero es recomendable su uso. Las verticles son “trozos” de código que se deployan en una instancia de Vert.x. Es una arquitectura muy similar al modelo de actores. Normalmente las aplicaciones desarrolladas en Vert.x están compuestas de varias verticles ejecutándose en una o varias instancias y comunicándose entre ellas mediante el Event Bus.

Las verticles deben implementar la interfaz Verticle, aunque normalmente es más sencillo extender la clase abstracta AbstractVerticle en la que solo hay que sobreescribir el método start.

Veamos como se crearían los ejemplos anteriores con verticles. Comencemos creando un nuevo proyecto en el que crearemos una nueva clase HelloWorldServerVerticle con el siguiente contenido:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package xyz.josehernandez.server;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;

public class HelloWorldServerVerticle extends AbstractVerticle {
    @Override
    public void start(Future<Void> future) throws Exception {
        super.start();

        vertx.createHttpServer()
            .requestHandler(req -> {
                req.response().end("Hello World!");
            })
            .listen(8080);

        future.complete();
    }
}

El código es identico al que hemos visto anteriormente, pero en esta ocasión está incluido dentro de la función start que se sobreescribe de la clase base AbstractVerticle. Este método recibe como parámetro un Future sobre el que ejecutaremos el método complete si la inicialización se ha realizado correctamente o fail se se ha detectado algún error. En este ejemplo, por simplicidad, simplemente creamos el servidor Http y resolvemos el Future llamando a complete.

El fichero pom.xml para este ejemplo es el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    ...
    <groupId>xyz.josehernandez</groupId>
    <artifactId>hello-world-server-verticle</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    ...
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.3</version>
            <executions>
                ...
                <transformer implementation="org.apache.maven.plugins.
                        shade.resource.ManifestResourceTransformer">
                    <manifestEntries>
                        <Main-Class>io.vertx.core.Launcher</Main-Class>
                        <Main-Verticle>
                            xyz.josehernandez.server.HelloWorldServerVerticle
                        </Main-Verticle>
                    </manifestEntries>
                </transformer>
                ...
            </executions>
        </plugin>
    ...

En esta ocasión tenemos que fijarnos que dentro de la sección manifestEntries del componente transformer tenemos dos entradas, una Main-Class que indica que la clase principal a ejecutar es Launcher y Main-Verticle donde indicamos cual es la verticle principal a arrancar.

De nuevo para crear el .jar ejecutaremos en un terminal:

> mvn clean package

Y para arrancar el servidor ejecutamos:

> java -jar hello-world-server-verticle-1.0-SNAPSHOT-fat.jar

Aquí está disponible el código del server con verticles.

Para el ejemplo del cliente crearemos la clase HelloWorldClientVerticle con el contenido:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package xyz.josehernandez.client;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Vertx;

public class HelloWorldClientVerticle extends AbstractVerticle {
    @Override
    public void start(Future<Void> future) throws Exception {
        super.start();

        Vertx.vertx().createHttpClient()
            .getNow(8080, "localhost", "/", res -> {
                res.bodyHandler(buffer -> {
                    System.out.println(buffer);
                });
            });

        future.complete();
    }
}

Y su fichero pom.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    ...
    <groupId>xyz.josehernandez</groupId>
    <artifactId>hello-world-client-verticle</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    ...
    <transformer implementation="org.apache.maven.plugins.
            shade.resource.ManifestResourceTransformer">
        <manifestEntries>
            <Main-Class>io.vertx.core.Launcher</Main-Class>
            <Main-Verticle>
                xyz.josehernandez.client.HelloWorldClientVerticle
            </Main-Verticle>
        </manifestEntries>
    </transformer>
    ...

De nuevo para crear el .jar:

> mvn clean package

Y teniendo en ejecución el ejemplo del servidor abrimos un nuevo terminal y ejecutamos:

> java -jar hello-world-client-verticle-1.0-SNAPSHOT-fat.jar

Aquí el código del cliente con verticles.

Tipos de verticles

Una vez que hemos visto como crear una verticle en la aplicación, vamos a ver los dos tipos principales de verticles que dispone Vert.x:

  • Standard verticle: estas verticles son las más comunes, cuando se crean se le asigna un hilo del Event Loop y todo el código de la verticle se ejecuta siempre en el mismo Event Loop. Lo que evita tener que tratar con problemas de hilos como son la sincronización, condiciones de carreras y deadlocks ya que es el propio Vert.x quien se encarga de realizarlo. Los ejemplos que hemos visto hasta ahora usan este tipo de verticles.

  • Worker verticle: estas se ejecutan usando un thread del worker pool. Una instancia nunca se ejecuta concurrentemente por más de un hilo. Estas instancias estan diseñadas para ejecutar código bloqueante.

Existen otro tipo de verticle que no se suelen usar mucho, las multi-threaded worker. Estás se ejecutan usando también un thread del worker pool, pero en esta ocasión, una instancia puede ser ejecutada concurrentemente por diferentes hilos. Estás verticles son una característica avanzada y la mayoría de las aplicaciones no la necesitan. Pero en caso de querer usarlas hay que tener en cuenta de mantener la verticle en un estado consistente usando las técnicas de programación Multi-threaded

Las verticles se pueden deployar y hacerles undeploy desde el código. Para deployar una vértice simplemente tenemos que llamar al método deployVerticle:

1
2
3
4
Verticle verticleSample = new VerticleSample();
vertx.deployVerticle(verticleSample);
// o
vertx.deplotVerticle("joseahernandez.VertxSample.verticleSample")

Para el caso de una verticle de tipo worker hay que indicar que es de este tipo antes de deployarla:

1
2
DeploymentOptions options = new DeploymentOptions().setWorker(true);
vertx.deployVerticle("xyz.josehernandez.MyWorkerVerticle", options);

Además, para saber si la vértice se ha deplorado correctamente, podemos añadirle un handler a la llamada para comprobar su resultado:

1
2
3
4
5
6
7
8
vertx.deployVerticle(verticleSample.class.getName(), res -> {
    if (res.succeeded()) {
        System.out.println("Verticle deployada satisfactoriamente con identificador " + 
            res.result());
    } else {
        res.cause().printStackTrace();
    }
});

Para hacer undeploy de las verticles tenemos el método undeploy al que se le indicará el identificador de la vértice para undeployar y, en caso de que nos interese, un handler para saber si se ha podido undeployar correctamente.

1
2
3
4
5
6
7
8
9
vertx.undeploy(verticleId)
// o
vertx.undeploy(verticleId, res -> {
    if (res.succeeded()) {
        System.out.println("Verticle undeployed")
    } else {
        res.cause().printStackTrace();
    }
});

A la hora de deplorar una verticle, podemos indicar el número de instancias que queremos. De esta forma tenemos un fácil escalado por múltiples cores:

1
2
DeploymentOptions options = new DeploymentOptions().setInstances(3);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);

También podemos pasarle configuraciones a las verticles y obtener su valor dentro de ellas gracias al método config:

1
2
3
4
// Creación de una verticle con una configuración
JsonObject config = new JsonObject().put("name", "tim").put("directory", "/foo");
DeploymentOptions options = new DeploymentOptions().setConfig(config);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);
1
2
// Dentro de la verticle com.mycompany.MyOrderProcessorVerticle
System.out.println("Configuration: " + config().getString("name"));

Aquí podemos ver un ejemplo de como deployar y undeployar verticles según lo que hemos visto anteriormente.

Event Bus

El Event Bus permite comunicar diferentes partes de la aplicación entre ellas, aunque esten escritas en diferentes lenguajes o se encuentren en distintas instancias de Vert.x. También permite la comunicación del lado del cliente con JavaScript en un navegador.

Vert.x implementa tres tipos de mensajes en su Event Bus:

  • Punto a punto: el mensaje se envía a un único consumidor.
  • Publish / Subscribe: el mensaje es recibido por todos los consumidores que están escuchando el mensaje.
  • Request / Response: el mensaje se envía a un único consumidor y se permite responder con otro mensaje que le llegará al remitente inicial.

Los mensajes son enviados a direcciones que indicamos con cadenas de textos. En estas direcciones se registran los handlers que son los encargados de recibir los mensajes. Diferentes handlers pueden estas asociados a una dirección y un handler puede estar asociado a varias direcciones.

Para registrar un manejador en el event bus se usará el método consumer:

1
2
3
vertx.eventBus().consumer("eventbus.message", message ->
    System.out.println("The message value is: " + message.body().toString())
);

Para publicar un mensaje simplemente llamaremos a publish indicando la dirección y el mensaje que queremos enviar:

1
vertx.eventBus().publish("eventbus.message", "This is the message");

Mientras que si queremos enviar un mensaje a un único destinatario usaremos el método send:

1
vertx.eventBus().send("eventbus.message", "This message is send only to one consumer");

Para responder un mensaje usaremos en método reply:

1
2
3
vertx.eventBus().consumer("eventbus.message", message ->
    message.reply("This is the response")
);

Aquí podemos ver distintos ejemplos trabajando con el EventBus y mensajes Publish/Subscribe, Point to Point y Request/Response.

Vert.x Command Line

Como hemos visto hasta ahora, podemos arrancar nuestra aplicación Vert.x creando un fat jar y ejecutándolo con el comando java -jar myApp.jar . Pero Vert.x además nos ofrece una linea de comando que podemos descargar de aquí y usarla para arrancar nuestra aplicación con el siguiente comando:

> vertx run io.vertx.example.MyVerticle -cp my-verticle.jar

Pero no solo nos permite arrancar nuestra aplicación a partir de un JAR si no que ademas podemos arrancar un fichero .java directamente:

> vertx run MyVerticle.java

Incluso un fichero .class

> vertx run io.vertx.example.MyVerticle

Algunos de los parámetros más interesantes que podemos usar usar con el comando run son:

  • -conf : donde podemos pasar un fichero de configuración para el verticle.
  • -instances : donde podemos indicar el número de instancias de la verticle que queremos que se ejecuten.
  • -worker: si queremos que la verticle sea un worker en vez de una verticle normal.
  • -cluster: para levantar la infancia de Vert.x en modo de cluster.
  • -ha: levantar la verticle en alta disponibilidad. Esto quiere decir que si por cualquier cosa la verticle se cae, automáticamente se migra a otra instancia.

Además del comando run, también tenemos disponibles los comandos:

  • start: para arrancar una aplicación vert.x en background.
  • stop: para detener una aplicación.
  • list: para listar las aplicaciones que están arrancadas.

Conclusión

Hasta aquí esta primera entrada sobre Vert.x, aunque solo se han visto conceptos básicos de esta herramienta, nos podemos hacer idea del gran rendimiento que podemos obtener en nuestras aplicaciones si hacemos uso de ella. En posteriores entradas hablaré sobre otros componentes que le darán mucha más funcionalidad a nuestras aplicaciones.