How to Configure RabbitMQ Message Queues for Order Processing in the Mall Project
The Mall project implements delayed order cancellation using a direct exchange paired with a TTL (dead-letter) queue pattern in Spring AMQP, ensuring reliable asynchronous processing of timeout events.
To configure RabbitMQ message queues for order processing in the macrozheng/mall e-commerce platform, you implement a deferred cancellation workflow using Spring Boot AMQP auto-configuration. The architecture relies on two distinct exchanges—one for immediate processing and one for delayed messages—bound to separate queues with dead-letter routing. This pattern guarantees that unpaid orders are automatically cancelled after a configurable timeout without blocking the main application thread.
Architecture Overview
The Mall application uses a dual-exchange topology to handle order timeouts gracefully:
mall.order.direct– The primary exchange that routes expired messages to the consumer queue.mall.order.direct.ttl– The TTL exchange that receives messages with expiration headers and forwards them to the dead-letter queue.
Messages are published to the TTL exchange with an x-message-ttl value. When the TTL expires, RabbitMQ automatically routes the message to the mall.order.cancel queue where a listener processes the cancellation logic.
Step-by-Step RabbitMQ Configuration
Define Exchanges and Queues in RabbitMqConfig.java
The core configuration resides in mall-portal/src/main/java/com/macro/mall/portal/config/RabbitMqConfig.java. This class declares the durable direct exchanges, the primary consumer queue, and the delayed queue with dead-letter arguments.
@Configuration
public class RabbitMqConfig {
@Bean
DirectExchange orderDirect() {
return ExchangeBuilder.directExchange(QueueEnum.QUEUE_ORDER_CANCEL.getExchange())
.durable(true)
.build();
}
@Bean
DirectExchange orderTtlDirect() {
return ExchangeBuilder.directExchange(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange())
.durable(true)
.build();
}
@Bean
public Queue orderQueue() {
return new Queue(QueueEnum.QUEUE_ORDER_CANCEL.getName());
}
@Bean
public Queue orderTtlQueue() {
return QueueBuilder.durable(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getName())
.withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_ORDER_CANCEL.getExchange())
.withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey())
.build();
}
@Bean
Binding orderBinding(DirectExchange orderDirect, Queue orderQueue) {
return BindingBuilder.bind(orderQueue)
.to(orderDirect)
.with(QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey());
}
@Bean
Binding orderTtlBinding(DirectExchange orderTtlDirect, Queue orderTtlQueue) {
return BindingBuilder.bind(orderTtlQueue)
.to(orderTtlDirect)
.with(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey());
}
}
The orderTtlQueue bean is critical: the x-dead-letter-exchange and x-dead-letter-routing-key arguments instruct RabbitMQ to route expired messages to the primary exchange with the correct routing key.
Centralize Queue Definitions with QueueEnum.java
To maintain consistency across producers and consumers, the Mall project centralizes routing metadata in mall-portal/src/main/java/com/macro/mall/portal/domain/QueueEnum.java.
@Getter
public enum QueueEnum {
QUEUE_ORDER_CANCEL("mall.order.direct", "mall.order.cancel", "mall.order.cancel"),
QUEUE_TTL_ORDER_CANCEL("mall.order.direct.ttl", "mall.order.cancel.ttl", "mall.order.cancel.ttl");
private final String exchange;
private final String name;
private final String routeKey;
QueueEnum(String exchange, String name, String routeKey) {
this.exchange = exchange;
this.name = name;
this.routeKey = routeKey;
}
}
Using this enum prevents hard-coded string literals and ensures that the dead-letter configuration in RabbitMqConfig.java remains synchronized with the producer logic.
Send Delayed Cancellation Messages
The CancelOrderSender component in mall-portal/src/main/java/com/macro/mall/portal/component/CancelOrderSender.java publishes order IDs to the TTL exchange with a custom expiration header.
@Component
public class CancelOrderSender {
@Autowired
private AmqpTemplate amqpTemplate;
public void sendMessage(Long orderId, long delayMs) {
amqpTemplate.convertAndSend(
QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange(),
QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey(),
orderId,
message -> {
message.getMessageProperties()
.setExpiration(String.valueOf(delayMs));
return message;
});
}
}
The MessagePostProcessor lambda sets the expiration property to the delay in milliseconds. RabbitMQ holds the message in the mall.order.cancel.ttl queue until the TTL expires, then forwards it to mall.order.direct using the dead-letter routing key.
Consume Messages with CancelOrderReceiver.java
The consumer implementation in mall-portal/src/main/java/com/macro/mall/portal/component/CancelOrderReceiver.java listens on the primary queue and delegates cancellation to the order service.
@Component
@RabbitListener(queues = "mall.order.cancel")
public class CancelOrderReceiver {
@Autowired
private OmsPortalOrderService portalOrderService;
@RabbitHandler
public void handle(Long orderId) {
portalOrderService.cancelOrder(orderId);
}
}
The @RabbitListener annotation binds this method to the mall.order.cancel queue. When the dead-letter routing delivers the expired message, Spring AMQP deserializes the order ID and invokes the cancellation business logic.
Spring Boot Application Properties
The Mall project exposes the logical queue name through application.yml in mall-portal/src/main/resources/application.yml, allowing environment-specific overrides.
rabbitmq:
queue:
name:
cancelOrder: cancelOrderQueue
While the physical queue names are defined in QueueEnum.java, this property provides a hook for external monitoring tools or administrative interfaces to reference the cancellation queue consistently.
Docker Deployment Configuration
For local development or production deployment, the project includes a RabbitMQ service definition in document/docker/docker-compose-app.yml.
services:
rabbitmq:
image: rabbitmq:3-management
container_name: rabbitmq
ports:
- "5672:5672"
- "15672:15672"
This configuration exposes the AMQP port (5672) for application connectivity and the management UI (15672) for monitoring queue depths and message rates.
Summary
- Topology: The Mall project uses a direct exchange with a companion TTL exchange to implement delayed message processing without dedicated delay plugins.
- Dead-Letter Routing: The
mall.order.cancel.ttlqueue declaresx-dead-letter-exchangeandx-dead-letter-routing-keyarguments to route expired messages to the active consumer queue. - Producer:
CancelOrderSenderpublishes to the TTL exchange with a per-messageexpirationheader set viaMessagePostProcessor. - Consumer:
CancelOrderReceiveruses@RabbitListener(queues = "mall.order.cancel")to process timeouts and triggerportalOrderService.cancelOrder(). - Maintainability: Centralized routing metadata in
QueueEnum.javaensures consistency across the producer, consumer, and configuration classes.
Frequently Asked Questions
How does the Mall project handle order timeout cancellation without blocking threads?
The application publishes order IDs to a TTL queue in RabbitMQ immediately after order creation. The message sits in the mall.order.cancel.ttl queue for the configured duration (typically 30 minutes). When the TTL expires, RabbitMQ routes the message to the mall.order.cancel queue where a background listener processes the cancellation asynchronously. This decouples the timeout logic from the HTTP request thread, improving throughput and resilience.
What is the purpose of the x-dead-letter-exchange argument in the queue configuration?
The x-dead-letter-exchange argument declared in RabbitMqConfig.java on the orderTtlQueue bean specifies which exchange should receive messages when they expire or are rejected. In the Mall implementation, this is set to mall.order.direct. Combined with x-dead-letter-routing-key, it ensures that timed-out order messages automatically flow from the delay queue to the active processing queue without additional application code.
Can I change the delay duration for order cancellation without redeploying the application?
Yes. While the queue-level TTL is fixed at declaration time, the Mall project uses per-message TTL via the setExpiration method in CancelOrderSender. This allows you to pass different delay values (in milliseconds) as the delayMs parameter when calling sendMessage(). To change the default cancellation window, modify the value passed to this method in the order creation service, or externalize it to a configuration property.
Where are the RabbitMQ connection properties configured in the Mall project?
Connection details (host, port, username, password) are configured in mall-portal/src/main/resources/application.yml under the spring.rabbitmq namespace. The Docker Compose file in document/docker/docker-compose-app.yml provides a reference RabbitMQ instance with default credentials suitable for development environments.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →