Introducción a la mensajería Quarkus con RabbitMQ
Esta guía demuestra cómo su aplicación Quarkus puede utilizar Quarkus Messaging para interactuar con RabbitMQ.
|
This technology is considered preview. In preview, backward compatibility and presence in the ecosystem is not guaranteed. Specific improvements might require changing configuration or APIs, and plans to become stable are under way. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker. For a full list of possible statuses, check our FAQ entry. |
Requisitos previos
To complete this guide, you need:
-
Roughly 15 minutes
-
An IDE
-
JDK 17+ installed with
JAVA_HOMEconfigured appropriately -
Apache Maven 3.9.12
-
Docker and Docker Compose or Podman, and Docker Compose
-
Optionally the Quarkus CLI if you want to use it
-
Optionally Mandrel or GraalVM installed and configured appropriately if you want to build a native executable (or Docker if you use a native container build)
Arquitectura
En esta guía, vamos a desarrollar dos aplicaciones que se comunican con un broker RabbitMQ. La primera aplicación envía una solicitud de cotización al intercambio de solicitudes de cotización RabbitMQ y consume mensajes de la cola de cotizaciones. La segunda aplicación recibe la solicitud de cotización y envía una cotización de vuelta.
La primera aplicación, el producer, permitirá al usuario solicitar algunas cotizaciones a través de un endpoint HTTP.
Para cada solicitud de cotización, se genera un identificador aleatorio que se devuelve al usuario, para poner la solicitud de cotización en pendiente.
Al mismo tiempo, el identificador de solicitud generado se envía al intercambio quote-requests.
La segunda aplicación, el processor, a su vez, leerá de la cola quote-requests, pondrá un precio aleatorio a la cotización, y lo enviará a un intercambio llamado quotes.
Por último, el producer leerá las cotizaciones y las enviará al navegador mediante eventos enviados por el servidor. Por tanto, el usuario verá el precio de la cotización actualizado de pendiente al precio recibido en tiempo real.
Solución
Le recomendamos que siga las instrucciones de las siguientes secciones y cree las aplicaciones paso a paso. No obstante, puede ir directamente al ejemplo completado.
Clone el repositorio Git: git clone https://github.com/quarkusio/quarkus-quickstarts.git o descargue un archivo.
La solución se encuentra en el rabbitmq-quickstart directorio.
Creación del proyecto Maven
En primer lugar, tenemos que crear dos proyectos: el productor y el procesador.
Para crear el proyecto productor, en un terminal ejecute:
For Windows users:
-
If using cmd, (don’t use backward slash
\and put everything on the same line) -
If using Powershell, wrap
-Dparameters in double quotes e.g."-DprojectArtifactId=rabbitmq-quickstart-producer"
Este comando crea la estructura del proyecto y selecciona las dos extensiones de Quarkus que vamos a utilizar:
-
El conector RabbitMQ de mensajería reactiva
-
Quarkus REST (antes RESTEasy Reactive) y su soporte Jackson para manejar cargas útiles JSON
|
Si ya tiene configurado su proyecto Quarkus, puede añadir la extensión CLI
Maven
Gradle
Esto añadirá lo siguiente a su pom.xml
build.gradle
|
Para crear el proyecto del procesador, desde el mismo directorio, ejecute:
For Windows users:
-
If using cmd, (don’t use backward slash
\and put everything on the same line) -
If using Powershell, wrap
-Dparameters in double quotes e.g."-DprojectArtifactId=rabbitmq-quickstart-processor"
En ese momento deberías tener la siguiente estructura:
.
├── rabbitmq-quickstart-processor
│ ├── README.md
│ ├── mvnw
│ ├── mvnw.cmd
│ ├── pom.xml
│ └── src
│ └── main
│ ├── docker
│ ├── java
│ └── resources
│ └── application.properties
└── rabbitmq-quickstart-producer
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
└── main
├── docker
├── java
└── resources
└── application.properties
Abra los dos proyectos en su IDE favorito.
El objeto Cotización
La clase Quote se utilizará en los proyectos producer y processor.
En aras de la simplicidad, duplicaremos la clase.
En ambos proyectos, cree el archivo src/main/java/org/acme/rabbitmq/model/Quote.java, con el siguiente contenido:
package org.acme.rabbitmq.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
public class Quote {
public String id;
public int price;
/**
* Default constructor required for Jackson serializer
*/
public Quote() { }
public Quote(String id, int price) {
this.id = id;
this.price = price;
}
@Override
public String toString() {
return "Quote{" +
"id='" + id + '\'' +
", price=" + price +
'}';
}
}
La representación JSON de los objetos Quote se utilizará en los mensajes enviados a las colas RabbitMQ
y también en los eventos enviados por el servidor a los clientes del navegador.
Quarkus tiene capacidades incorporadas para tratar con mensajes JSON RabbitMQ.
|
@RegisterForReflection
La anotación |
Envío de solicitud de presupuesto
Dentro del proyecto producer, localice el archivo generado src/main/java/org/acme/rabbitmq/producer/QuotesResource.java y actualice el contenido para que sea:
package org.acme.rabbitmq.producer;
import java.util.UUID;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.acme.rabbitmq.model.Quote;
import org.eclipse.microprofile.reactive.messaging.Channel;
import org.eclipse.microprofile.reactive.messaging.Emitter;
import io.smallrye.mutiny.Multi;
@Path("/quotes")
public class QuotesResource {
@Channel("quote-requests") Emitter<String> quoteRequestEmitter; (1)
/**
* Endpoint to generate a new quote request id and send it to "quote-requests" channel (which
* maps to the "quote-requests" RabbitMQ exchange) using the emitter.
*/
@POST
@Path("/request")
@Produces(MediaType.TEXT_PLAIN)
public String createRequest() {
UUID uuid = UUID.randomUUID();
quoteRequestEmitter.send(uuid.toString()); (2)
return uuid.toString();
}
}
| 1 | Inyectar una Mensajería Reactiva Emitter para enviar mensajes al canal quote-requests. |
| 2 | En una solicitud POST, genere un UUID aleatorio y envíelo a la cola RabbitMQ utilizando el emisor. |
Este canal se asigna a un intercambio RabbitMQ utilizando la configuración que añadiremos al archivo application.properties.
Abra el archivo src/main/resource/application.properties y añada:
# Configure the outgoing RabbitMQ exchange `quote-requests`
mp.messaging.outgoing.quote-requests.connector=smallrye-rabbitmq
mp.messaging.outgoing.quote-requests.exchange.name=quote-requests
Todo lo que necesitamos especificar es el conector smallrye-rabbitmq.
Por defecto, la mensajería reactiva asigna el nombre del canal quote-requests al mismo nombre de intercambio RabbitMQ.
Procesamiento de solicitudes de cotización
Ahora vamos a consumir la solicitud de cotización y a dar un precio.
Dentro del proyecto processor, localice el archivo src/main/java/org/acme/rabbitmq/processor/QuoteProcessor.java y añada lo siguiente:
package org.acme.rabbitmq.processor;
import java.util.Random;
import jakarta.enterprise.context.ApplicationScoped;
import org.acme.rabbitmq.model.Quote;
import org.eclipse.microprofile.reactive.messaging.Incoming;
import org.eclipse.microprofile.reactive.messaging.Outgoing;
import io.smallrye.reactive.messaging.annotations.Blocking;
/**
* A bean consuming data from the "quote-requests" RabbitMQ queue and giving out a random quote.
* The result is pushed to the "quotes" RabbitMQ exchange.
*/
@ApplicationScoped
public class QuoteProcessor {
private Random random = new Random();
@Incoming("requests") (1)
@Outgoing("quotes") (2)
@Blocking (3)
public Quote process(String quoteRequest) throws InterruptedException {
// simulate some hard-working task
Thread.sleep(1000);
return new Quote(quoteRequest, random.nextInt(100));
}
}
| 1 | Indica que el método consume los elementos del canal requests |
| 2 | Indica que los objetos devueltos por el método se envían al canal quotes |
| 3 | Indica que el procesamiento está bloqueando y no puede ser ejecutado en el hilo de la llamada. |
El método process es llamado por cada mensaje RabbitMQ de la cola quote-requests, y enviará un objeto Quote al intercambio quotes.
Al igual que en el ejemplo anterior, debemos configurar los conectores en el archivo application.properties.
Abra el archivo src/main/resources/application.properties y añada:
# Configure the incoming RabbitMQ queue `quote-requests`
mp.messaging.incoming.requests.connector=smallrye-rabbitmq
mp.messaging.incoming.requests.queue.name=quote-requests
mp.messaging.incoming.requests.exchange.name=quote-requests
# Configure the outgoing RabbitMQ exchange `quotes`
mp.messaging.outgoing.quotes.connector=smallrye-rabbitmq
mp.messaging.outgoing.quotes.exchange.name=quotes
Observe que en este caso tenemos una configuración de conectores entrantes y otra de conectores salientes, cada una con un nombre distinto. Las propiedades de la configuración están estructuradas como sigue:
mp.messaging.[outgoing|incoming].{channel-name}.property=value
El segmento channel-name debe coincidir con el valor establecido en las anotaciones @Incoming y @Outgoing:
-
quote-requests→ Cola RabbitMQ de la que leemos las solicitudes de cotización -
quotes→ Intercambio RabbitMQ en el que escribimos las cotizaciones
Recepción de presupuestos
Volvamos a nuestro proyecto producer. Modifiquemos el QuotesResource para consumir cotizaciones, vinculémoslo a un endpoint HTTP para enviar eventos a los clientes:
import io.smallrye.mutiny.Multi;
//...
@Channel("quotes") Multi<Quote> quotes; (1)
/**
* Endpoint retrieving the "quotes" queue and sending the items to a server sent event.
*/
@GET
@Produces(MediaType.SERVER_SENT_EVENTS) (2)
public Multi<Quote> stream() {
return quotes; (3)
}
| 1 | Inyecta el canal quotes utilizando el calificador @Channel |
| 2 | Indica que el contenido se envía utilizando Server Sent Events |
| 3 | Devuelve el flujo (Reactive Stream) |
De nuevo necesitamos configurar el canal entrante quotes dentro del proyecto producer.
Añada lo siguiente dentro del archivo application.properties:
# Configure the outgoing `quote-requests` queue
mp.messaging.outgoing.quote-requests.connector=smallrye-rabbitmq
# Configure the incoming `quotes` queue
mp.messaging.incoming.quotes.connector=smallrye-rabbitmq
La página HTML
El toque final, la página HTML que lee los precios convertidos usando SSE.
Cree dentro del proyecto producer el archivo src/main/resources/META-INF/resources/quotes.html, con el siguiente contenido:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Quotes</title>
<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly.min.css">
<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly-additions.min.css">
</head>
<body>
<div class="container">
<div class="card">
<div class="card-body">
<h2 class="card-title">Quotes</h2>
<button class="btn btn-info" id="request-quote">Request Quote</button>
<div class="quotes"></div>
</div>
</div>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$("#request-quote").click((event) => {
fetch("/quotes/request", {method: "POST"})
.then(res => res.text())
.then(qid => {
var row = $(`<h4 class='col-md-12' id='${qid}'>Quote # <i>${qid}</i> | <strong>Pending</strong></h4>`);
$(".quotes").append(row);
});
});
var source = new EventSource("/quotes");
source.onmessage = (event) => {
var json = JSON.parse(event.data);
$(`#${json.id}`).html(function(index, html) {
return html.replace("Pending", `\$\xA0${json.price}`);
});
};
</script>
</html>
Aquí no hay nada espectacular. En cada cita recibida, actualiza la página.
Ponerlo en marcha
Sólo tienes que ejecutar ambas aplicaciones utilizando:
mvn -f rabbitmq-quickstart-producer quarkus:dev
Y, en una terminal separada:
mvn -f rabbitmq-quickstart-processor quarkus:dev
Quarkus inicia un broker RabbitMQ automáticamente, configura la aplicación y comparte la instancia del broker entre diferentes aplicaciones. Consulte Servicios de desarrollo para RabbitMQ para obtener más detalles.
Abra http://localhost:8080/quotes.html en su navegador y solicite algunos presupuestos haciendo clic en el botón.
Ejecución en modo JVM o nativo
Cuando no esté ejecutando en modo dev o test, necesitará iniciar su broker RabbitMQ.
Puede seguir las instrucciones del sitio web de RabbitMQ Docker o crear un archivo docker-compose.yaml con el siguiente contenido:
version: '2'
services:
rabbit:
image: rabbitmq:3.12-management
ports:
- "5672:5672"
networks:
- rabbitmq-quickstart-network
producer:
image: quarkus-quickstarts/rabbitmq-quickstart-producer:1.0-${QUARKUS_MODE:-jvm}
build:
context: rabbitmq-quickstart-producer
dockerfile: src/main/docker/Dockerfile.${QUARKUS_MODE:-jvm}
environment:
RABBITMQ_HOST: rabbit
RABBITMQ_PORT: 5672
ports:
- "8080:8080"
networks:
- rabbitmq-quickstart-network
processor:
image: quarkus-quickstarts/rabbitmq-quickstart-processor:1.0-${QUARKUS_MODE:-jvm}
build:
context: rabbitmq-quickstart-processor
dockerfile: src/main/docker/Dockerfile.${QUARKUS_MODE:-jvm}
environment:
RABBITMQ_HOST: rabbit
RABBITMQ_PORT: 5672
networks:
- rabbitmq-quickstart-network
networks:
rabbitmq-quickstart-network:
name: rabbitmq-quickstart
Observe cómo se configura la ubicación del broker RabbitMQ.
Las propiedades rabbitmq-host y rabbitmq-port (variables de entorno AMQP_HOST y AMQP_PORT) configuran la ubicación.
En primer lugar, asegúrese de que ha detenido las aplicaciones, y construya ambas aplicaciones en modo JVM con:
mvn -f rabbitmq-quickstart-producer clean package
mvn -f rabbitmq-quickstart-processor clean package
Una vez empaquetado, ejecute docker compose up --build . La interfaz de usuario se expone en http://localhost:8080/quotes.html .
Para ejecutar tus aplicaciones como nativas, primero tenemos que construir los ejecutables nativos:
mvn -f rabbitmq-quickstart-producer package -Dnative -Dquarkus.native.container-build=true
mvn -f rabbitmq-quickstart-processor package -Dnative -Dquarkus.native.container-build=true
El -Dquarkus.native.container-build=true instruye a Quarkus para construir ejecutables nativos de Linux 64bits, que pueden ejecutarse dentro de contenedores. A continuación, ejecute el sistema utilizando:
export QUARKUS_MODE=native
docker compose up --build
Como antes, la interfaz de usuario está expuesta en http://localhost:8080/quotes.html
Ir más allá
Esta guía ha mostrado cómo puede interactuar con RabbitMQ utilizando Quarkus. Utiliza SmallRye Reactive Messaging para construir aplicaciones de flujo de datos.
Si ha hecho el de Kafka, se habrá dado cuenta de que se trata del mismo código. La única diferencia es la configuración del conector y el mapeo JSON.