Configuración y Uso de RabbitMQ en Aplicaciones Spring
La integración de RabbitMQ con Spring Boot permite el manejo eficiente de mensajes asíncronos en sistemas distribuidos. A continuación, se detallan los pasos esenciales para configurar tanto el productor como el consumidor, asegurando la confiabilidad en la entrega de mensajes.
Configuración del Productor
En el lado del productor, es crucial habilitar confirmaciones de publicación y retornos de mensajes para garantizar la integridad. Configurar la plantilla de RabbitMQ con mandatory=true asegura que los mensajes no se pierdan si no son enrutables. Atributos adicionales como reintentos de envío, tiempos de espera y límites de intentos pueden ajustarse según los requisitos de la aplicación.
# application.properties para el productor
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=secret
spring.rabbitmq.virtual-host=/
spring.rabbitmq.connection-timeout=10000
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.template.mandatory=true
Configuración del Consumidor
El consumidor debe operar en modo de confirmación manual (ACK) para controlar el procesamiento de mensajes. Esto permite reintentar mensajes fallidos o registrar errores según la lógica de negocio. Además, se puede definir la concurrencia mediante parámetros como el número mínimo y máximo de escuchas para optimizar el rendimiento.
# application.properties para el consumidor
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.concurrency=3
spring.rabbitmq.listener.simple.max-concurrency=8
Uso de Anotaciones para Consumidores
La anotación @RabbitListener simplifica la configuración del consumidor al permitir definir colas, intercambios y enlaces directamente en el código. Sin embargo, para una mayor flexibilidad, se recomienda externalizar estas configuraciones en archivos de propiedades. La anotación combina múltiples configuraciones, facilitando la implementación de escuchas complejas.
Estructura del Proyecto
Se asume un proyecto modular con un módulo común para entidades compartidas. Por ejemplo, una clase de dominio para representar órdenes de compra.
package com.example.common.model;
import java.io.Serializable;
public class PurchaseOrder implements Serializable {
private String orderId;
private String productDescription;
public PurchaseOrder() {}
public PurchaseOrder(String orderId, String productDescription) {
this.orderId = orderId;
this.productDescription = productDescription;
}
// Getters y setters omitidos por brevedad
}
Implementación del Consumidor
El consumidor se configura mediante anotaciones para vincular colas e intercambios. En este ejemplo, se definen dos escuchas: una para mensajes genéricos y otra para objetos de orden, con confirmación manual en ambos casos.
package com.example.consumer.listener;
import com.example.common.model.PurchaseOrder;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class OrderListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "orders.queue", durable = "true"),
exchange = @Exchange(value = "orders.exchange", durable = "true", type = "topic"),
key = "order.*"
))
@RabbitHandler
public void handleGenericMessage(Message<string> message, Channel channel) throws Exception {
Long tag = (Long) message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
System.out.println("Mensaje recibido: " + message.getPayload());
channel.basicAck(tag, false);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "${app.queue.name}", durable = "${app.queue.durable}"),
exchange = @Exchange(value = "${app.exchange.name}", durable = "${app.exchange.durable}", type = "${app.exchange.type}"),
key = "${app.routing.key}"
))
@RabbitHandler
public void handleOrderMessage(@Payload PurchaseOrder order, Channel channel,
@Headers Map<string object=""> headers) throws Exception {
Long tag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
System.out.println("Orden procesada con ID: " + order.getOrderId());
channel.basicAck(tag, false);
}
}
</string></string>
Implementación del Productor
El productor utiliza RabbitTemplate para enviar mensajes, con callbacks para confirmaciones y retornos. Se pueden enviar tento mensajes simples como objetos serializados, asegurando un identificader único para cada mensaje.
package com.example.producer.sender;
import com.example.common.model.PurchaseOrder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.UUID;
@Component
public class MessageSender {
private final RabbitTemplate rabbitTemplate;
public MessageSender(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
configureCallbacks();
}
private void configureCallbacks() {
rabbitTemplate.setConfirmCallback((correlation, ack, cause) -> {
if (!ack) {
System.err.println("Error en confirmación: " + cause);
}
});
rabbitTemplate.setReturnCallback((message, code, text, exchange, key) -> {
System.err.println("Mensaje retornado: " + text);
});
}
public void sendSimpleMessage(String content, Map<String, Object> headers) {
Message<String> message = MessageBuilder.createMessage(content,
new org.springframework.messaging.MessageHeaders(headers));
CorrelationData correlation = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("orders.exchange", "order.simple", message, correlation);
}
public void sendPurchaseOrder(PurchaseOrder order) {
CorrelationData correlation = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("orders.exchange", "order.create", order, correlation);
}
}
Pruebas de Integración
Para verificar la integración, se pueden ejecutar pruebas unitarias que envíen mensajes al productor y validen su recepción en el consumidor. Asegúrese de que el servidor RabbitMQ esté en ejecución antes de realizar las pruebas.
package com.example.producer.test;
import com.example.common.model.PurchaseOrder;
import com.example.producer.sender.MessageSender;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
public class SenderTests {
@Autowired
private MessageSender messageSender;
@Test
public void testSimpleMessage() {
Map<String, Object> props = new HashMap<>();
props.put("timestamp", LocalDateTime.now().toString());
messageSender.sendSimpleMessage("Mensaje de prueba", props);
}
@Test
public void testOrderMessage() {
PurchaseOrder order = new PurchaseOrder("ORD-001", "Laptop");
messageSender.sendPurchaseOrder(order);
}
}