# How to Configure RabbitMQ Message Queues for Order Processing in the Mall Project

> Learn to configure RabbitMQ message queues for order processing in the Mall project. Implement reliable async event handling with Spring AMQP TTL dead-letter queues.

- Repository: [macro/mall](https://github.com/macrozheng/mall)
- Tags: how-to-guide
- Published: 2026-02-28

---

**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`](https://github.com/macrozheng/mall/blob/main/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.

```java
@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`](https://github.com/macrozheng/mall/blob/main/mall-portal/src/main/java/com/macro/mall/portal/domain/QueueEnum.java).

```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`](https://github.com/macrozheng/mall/blob/main/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`](https://github.com/macrozheng/mall/blob/main/mall-portal/src/main/java/com/macro/mall/portal/component/CancelOrderSender.java) publishes order IDs to the TTL exchange with a custom expiration header.

```java
@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`](https://github.com/macrozheng/mall/blob/main/mall-portal/src/main/java/com/macro/mall/portal/component/CancelOrderReceiver.java) listens on the primary queue and delegates cancellation to the order service.

```java
@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`](https://github.com/macrozheng/mall/blob/main/application.yml) in [`mall-portal/src/main/resources/application.yml`](https://github.com/macrozheng/mall/blob/main/mall-portal/src/main/resources/application.yml), allowing environment-specific overrides.

```yaml
rabbitmq:
  queue:
    name:
      cancelOrder: cancelOrderQueue

```

While the physical queue names are defined in [`QueueEnum.java`](https://github.com/macrozheng/mall/blob/main/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`](https://github.com/macrozheng/mall/blob/main/document/docker/docker-compose-app.yml).

```yaml
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`](https://github.com/macrozheng/mall/blob/main/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`](https://github.com/macrozheng/mall/blob/main/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`](https://github.com/macrozheng/mall/blob/main/mall-portal/src/main/resources/application.yml) under the `spring.rabbitmq` namespace. The Docker Compose file in [`document/docker/docker-compose-app.yml`](https://github.com/macrozheng/mall/blob/main/document/docker/docker-compose-app.yml) provides a reference RabbitMQ instance with default credentials suitable for development environments.