Architecture
Message Queue + Dead Letter Exchange
Put a durable queue between producer and consumer so spikes, crashes, and "poison" messages never take the whole system down.
Calling the consumer synchronously
The producer calls the consumer directly and waits. One slow dependency, one crash, or one bad message stalls — or loses — everything behind it.
function placeOrder(order) {
emailService.send(order); // blocks here!
// spike of orders → emailService overloaded
// crash mid-call → order is lost forever
// one bad order → retried forever, blocks everyone
}
Decouple with a durable queue — and a DLX for failures
Producer publishes and moves on. Consumer pulls at its own pace and
acks on success. Repeated failure → nack → retried, then
routed to a Dead Letter Exchange instead of blocking the queue.
function consume(msg) {
try {
handle(msg);
channel.ack(msg);
} catch (e) {
msg.retries++;
if (msg.retries >= MAX_RETRIES)
channel.deadLetter(msg); // → DLX
else
channel.nack(msg, true); // requeue
}
}
Produce, process, and watch a poison message fall through
Message #1 flows normally. Message #2 is "poison" — it fails 3 times, is retried each time, then gets routed to the DLQ instead of blocking the queue.
Follow the numbered messages top-to-bottom — #1 acks cleanly; #2 is nacked 3 times, then dead-lettered instead of stalling the queue.
Buffer, retry, isolate
- Decouples producer and consumer — neither needs the other to be up right now.
- Absorbs spikes: the queue is a buffer, so bursts don't overload the consumer.
- Durability + retries: messages survive crashes and get a bounded number of attempts.
- DLQ isolates poison messages for later inspection/replay instead of blocking everyone else.
- You already use it: RabbitMQ (exchanges + DLX), Amazon SQS (redrive policy), Kafka (retry topics).