Pensar en Quarkus te lleva a pensar en rapidez, velocidad, en un ¡ya! pero… ¿lo piensas de manera reactiva o secuencial? Secuencial es pensar de la forma que siempre nos enseñan, y reactiva es como acabamos terminando aprendiendo (queriendo o a la fuerza). Con Mutiny, las operaciones en Quarkus empiezan a sonar de forma reactiva para fluir en paralelo, utilizar varios hilos…

En este post navegaremos en los primeros pasos de Mutiny en Quarkus, un framework encargado de hacernos la vida más sencilla en un mundo reactivo y complicado de entender al principio. Iremos desde qué son los Uni y los Multi, a cómo transformar datos o manejar los errores. Te doy la bienvenida al mundo reactivo en Quarkus.

¿Por qué usar Mutiny?

1 Queremos (necesitamos) ir a la reactividad

Lo más importante es saber qué necesitamos, por encima del qué queremos. Podemos querer un proyecto basado en reactividad cuando realmente no lo necesitamos y sería como tener un motor de un Ferrari en un Seat 600.

Por lo tanto, si realmente necesitamos la programación reactiva en nuestro proyecto Quarkus, tenemos que dar ese salto a la reactividad.

2 Una vez decidido: ¿por qué Mutiny?

  • Mutiny es la API reactiva “predeterminada” en Quarkus ya que, actualmente, muchas extensiones y librerías (HTTP, REST, clientes, etc) ya exponen sus propias APIs devolviendo Uni o Multi, como podemos observar en la documentación oficial de Quarkus.
  • A su vez, Mutiny nos permite utilizar código basado en eventos y lo hace de una forma declarativa. Algo así como: “cuando esto ocurra, haz aquello”. De esta forma evita los callbacks manuales o los bloques de código secuencial.
  • Tiene una API clara con operadores encadenables que hacen que mejore su legibilidad frente a cadenas complejas de operadores genéricos
  • En entornos como GraalVM, Quarkus, junto con Mutiny, nos ayuda a eliminar código que no se usa (“dead-code elimination”).

3 Conceptos claves: Uni y Multi

Mutiny tiene dos conceptos claves y sobre los que gira todo:

  • Uni: representa una operación que emite uno o ningún resultado o, en otro caso no exitoso, falla. La traducción en RxJava sería el Mono.
  • Multi: representa un flujo de múltiples valores (0..N) o error en caso de fallo. Se asemeja a un Stream en la java secuencial y un Flux en la reactiva. Nos será útil para flujos de datos, streaming etc.

Teniendo claro qué es cada uno, ¿cómo trabajamos con ellos? ¿Qué operaciones podemos realizar?

Lo más sencillo será tirar de un ejemplo:

Uni.createFrom().item("Hola")
    .onItem()
    .transform(s -> s + " mundo")
    .onItem()
    .invoke(s->System.out.println("Emitido"+s))
.onFailure().recoverWithItem("Error por aquí");

En ese código ocurre lo siguiente:

  • Creamos un Uni (createFrom) que emitirá Hola cuando le corresponda.
  • Cuando el ítem llegue, lo transformamos (transform) a “Hola mundo” .
  • Ejecutamos la impresión del valor (invoke) para imprimir la cadena de texto.
  • En caso de fallo, se devolverá una cadena de texto “Error por aquí” (onFailure().recoverWithItem)-

Por lo tanto, los operadores más comunes en Mutiny son:

  • onItem() -> operador para actuar sobre el valor emitido, ya sea para transformar, filtrar…
  • onFailure() -> para gestionar los errores que pueden ocurrir durante la operación.
  • combine() -> para unir varios flujos. Por ejemplo:
Uni.combine().all()unis(...).combinedWith(...)

Otros operadores que utilizamos en muchos casos con Multi son los conocidos ya en Java como el merge(). concatenate(), select()... y muchos más que podemos encontrar aquí.

4 Preparar nuestro proyecto Quarkus para Mutiny

Una vez decidido que sí necesitamos Mutiny y la reactividad en nuestro proyecto Quarkus, iremos paso a paso (desde la creación del proyecto):

  1. Generamos nuestro proyecto Quarkus, ya sea vía code.quarkus.io o mvn.
  • code.quarkus.io -> Rellenamos los datos básicos y añadimos las siguientes extensiones: Mutiny y RESTEasy Reactive. Acto seguido, descargamos el proyecto y lo podemos importar en nuestro IDE.
  • Vía mvn -> Podemos utilizar el siguiente comando vía bash:
mvn io.quarkus.platform:quarkus-maven-plugin:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=mutiny-demo \
    -DclassName="com.example.GreetingResource" \
    -Dpath="/hello"
  1. Si no está la dependencia de Mutiny, la añadimos manualmente en nuestro pom:
<dependency>
  <groupId>io.quarkus</groupId>
 <artifactId>quarkus-mutiny</artifactId>
</dependency>

O vía mvn:

mvn quarkus:add-extension -Dextensions=mutiny
  1. Una vez generado el proyecto, vamos a crear un endpoint muy básico que devuelva un saludo, como se debe hacer cuando vemos a alguien:
package com.example;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import io.smallrye.mutiny.Uni;

@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public Uni<String> hello() {
        return Uni.createFrom().item("Hola desde Quarkus + Mutiny")
                  .onItem().transform(s -> s + " — vigente a " + System.currentTimeMillis());
    }
}
  • En este código: creamos un objetivo Uni (createFrom()) al que le asignamos la cadena de texto (.item): “Hola desde Quarkus + Mutiny”, y usamos onItem().transform() para transformar esa cadena de texto en otra conjunta y devolverla.
  • Cuando llamamos a ese endpoint (en nuestro caso, curl localhost:8080/hello) nos devuelve algo así: “Hola desde Quarkus + Mutiny — vigente a 169xxx...”.

Y así, ¡acabamos de hacer nuestro primer endpoint de Mutiny con Quarkus!

5 Uso de Multi para streams y listas

En nuestro primer endpoint nos basamos únicamente en los Uni así que, ahora, vamos a probar el otro elemento clave: Multi. Como recordatorio, utilizamos Multi cuando esperamos múltiples resultados (como streams o listas).

Si tuviésemos este endpoint llamado words:

@Path("/words")
public class WordsResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Multi<String> words() {
        return Multi.createFrom().items("uno", "dos", "tres")
            .onItem().transform(String::toUpperCase);
    }
}

Cuando hacemos la petición (localhost:8080/words), Quarkus nos va a enviar como stream JSON (si nuestro cliente/servidor lo admiten). También puedes combinar Multi con Uni para transformar cada elemento de nuestro Multi en uno individual Uni:

Multi.createFrom().items("a", "b", "c")
     .onItem().transformToUni(s -> Uni.createFrom().item(s + "_x"))
     .concatenate();

Otro ejemplo que nos podemos encontrar con los Multis es la combinación entre ellos mismos, es decir, ¡todo al Multi!

public Uni<String> combined() {
    Uni<String> u1 = Uni.createFrom().item("A").onItem().delayIt().by(Duration.ofMillis(100));
    Uni<String> u2 = Uni.createFrom().item("B").onItem().delayIt().by(Duration.ofMillis(200));

    return Uni.combine().all().unis(u1, u2)
        .combinedWith(list -> {
            String s1 = (String) list.get(0);
            String s2 = (String) list.get(1);
            return s1 + "-" + s2;
        });
}

En este ejemplo, combined() espera que ambas operaciones terminen y luego las combina. No devuelve el resultado hasta que ambas concluyan.

6 Errores comunes y cómo evitarlos

  • No suscribirse/retornar mal.

En Quarkus, si tenemos un endpoint definido que devuelve un Uni (por ejemplo, el GET que ponemos en los ejemplos), Quarkus se encarga de suscribirse de manera automática. Es decir, cuando el Uni emite un valor, Quarkus lo transforma en una respuesta HTTP (por ejemplo, un 200 OK con el contenido del Uni). No uses subscribe() manual fuera de contextos de prueba.

  • Bloquear hilos de I/O

No usamos llamadas bloqueantes dentro de nuestras aplicaciones (Thread.sleep, acceso a base de datos bloqueante…). Si necesitamos hacer algo bloqueante, la mejor forma es convertirlo en Uni ejecutado en otro hilo o usar .runSubscriptionOn(...).

  • Uso de estado mutante

Debemos evitar la mutación de listas o variables externas dentro de operadores, ya que puede producir resultados inesperados en modo reactivo.

  • No mezclar asincronía y sincronía

Diseñar siempre nuestras operaciones desde un principio con Uni y Multi para que todas devuelvan este tipo o trabajen directamente con ellas.

  • Pruebas, pruebas y ¡más pruebas!

Mutiny tiene gran soporte para test, por lo tanto, usemoslos.

Conclusión

Ya tenemos nuestros primeros cimientos para dar otro paso en la programación reactiva (como ya vimos en otros post enfocados en RxJava) pero esta vez con Quarkus.

Hemos conocido los Multi y los Uni, nuestros nuevos amigos, y hemos visto cómo combinarlos en diferentes operaciones. Si este ha sido tu primer contacto, posiblemente hayas sentido más el salto, ya que nos obliga a cambiar la forma de pensar que teníamos hasta ahora.

Sin embargo, una de las cosas más importantes que debemos hacer es probar y fallar, fallar y volver a fallar. Así conseguiremos aprender más sobre la programación reactiva y que deje de ser una desconocida. ¡Te leo en comentarios!

Referencias

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.