RabbitMQ Hands-On Lab: Build One End-to-End System
This lab gives you one working async system on your laptop.
What You Will Build
A mini order processing pipeline:
producersendsorder.createdmessages.workerconsumes orders and processes them.workerpublishes final status (order.processedororder.failed).notifierconsumes status messages and prints final result.- Failed messages go to a DLQ after retries.
By the end, you will have a real event flow running through RabbitMQ.
Definition of Done
- You can run all services.
- You can submit 5 orders and see processed/failure outcomes.
- You can kill the worker and observe redelivery.
- You can force failures and see DLQ behavior.
Lab Stack
- Language: Python
- Tooling:
uv - Broker: RabbitMQ 4+ (Docker)
- Library:
pika
Step 1: Project Setup (10 min)
-
Create files:
-
docker-compose.yml -
pyproject.toml(created byuv init) -
producer.py -
worker.py -
notifier.py -
Initialize project and dependency with
uv:
uv init --name rabbitmq-lab
uv add pika==1.3.2
- Put this in
docker-compose.yml:
version: "3.9"
services:
rabbitmq:
image: rabbitmq:4-management
ports:
- "5672:5672"
- "15672:15672"
- Start RabbitMQ:
docker compose up -d
- Open UI:
http://localhost:15672(guest/guest).
Step 2: RabbitMQ Topology (15 min)
Create these resources from code (preferred) or UI:
- Exchange
orders.x(direct) - Exchange
orders.dlx(direct) - Queue
orders.q - Queue
orders.processed.q - Queue
orders.failed.q - Queue
orders.dlq
Bindings:
orders.q<-orders.xwithorder.createdorders.processed.q<-orders.xwithorder.processedorders.failed.q<-orders.xwithorder.failedorders.dlq<-orders.dlxwithorder.dead
Queue args for orders.q:
x-dead-letter-exchange=orders.dlxx-dead-letter-routing-key=order.dead
Step 3: Producer (20 min)
producer.pyshould publish 5 JSON orders to routing keyorder.created.- Each message should include:
order_iduser_idamountshould_fail(set true for at least 1 order)retry_count(start at 0)- Mark messages persistent.
Success check:
- Running
uv run producer.pyprints “Published order …” 5 times.
Step 4: Worker (35 min)
worker.pyconsumes fromorders.qwith manual ACK.- For each message:
- Parse JSON
- Simulate processing (
sleep(1)) - If
should_failandretry_count < 2, republish same order withretry_count + 1toorder.created, ACK original. - If
should_failandretry_count >= 2, publish toorder.failed, ACK original. - Else publish to
order.processed, ACK original.
Important:
- Set
prefetch_count=1. - Use persistent publish for all outgoing messages.
Success check:
- You see logs for retries and final outcomes.
Step 5: Notifier (15 min)
notifier.pyconsumes bothorders.processed.qandorders.failed.q.- Print concise final messages, e.g.:
ORDER 1001 PROCESSEDORDER 1003 FAILED AFTER RETRIES- ACK all notifier messages.
Success check:
- After running producer, notifier shows both success and failure events.
Step 6: End-to-End Run (10 min)
Run in 3 terminals:
- Terminal A:
uv run worker.py - Terminal B:
uv run notifier.py - Terminal C:
uv run producer.py
Expected behavior:
- Normal orders finish in
orders.processed.qpath. - Failing orders retry 2 times then go
orders.failed.q.
Step 7: Failure Drill (10 min)
- Start worker and producer.
- Kill worker while processing a message.
- Restart worker.
- Verify unacked message is redelivered and processed.
Step 8: DLQ Drill (10 min)
- In worker, temporarily
basic_nack(requeue=False)for one special order. - Verify message appears in
orders.dlq. - Inspect message payload from RabbitMQ UI.
What You Learn (Concrete)
- RabbitMQ core flow (producer -> exchange -> queue -> consumer)
- Routing keys and direct exchange
- Manual ACK and redelivery
- Retry strategy and failure handling
- DLQ basics used in real systems