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.ttl queue declares x-dead-letter-exchange and x-dead-letter-routing-key arguments to route expired messages to the active consumer queue.
  • Producer: CancelOrderSender publishes to the TTL exchange with a per-message expiration header set via MessagePostProcessor.
  • Consumer: CancelOrderReceiver uses @RabbitListener(queues = "mall.order.cancel") to process timeouts and trigger portalOrderService.cancelOrder().
  • Maintainability: Centralized routing metadata in QueueEnum.java ensures 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →