DevConda — blog-workspace

Put RabbitMQ Between Microservices

This post focuses on the concept: what a message broker is, how it fits microservices (MSA), and when to use it instead of HTTP. Spring Boot snippets are short – enough to see how producers and consumers attach.

When you split a monolith into services – orders, payments, inventory, notifications – and call everything with REST, a few problems show up:

  • If the notification service is briefly down, the order API can look like it failed too.
  • Slow work (sending email) makes users wait on the "Place order" button.
  • Many services need to know "an order happened," so the order service must know every URL.

A message broker like RabbitMQ is a different tool from "call them on the phone now" (HTTP). It is closer to leaving a note or a parcel for later.

RabbitMQ in one picture

Role Job Analogy
Producer Sends a message Person dropping a "please do this" note
Exchange Decides which queues get it Post office sorter
Queue Holds messages until work starts Parcel locker
Consumer Takes messages and processes them Worker who opens the parcel
Order service (Producer) -> Exchange -> Queue -> Inventory / Notification (Consumers)

Consumers subscribe to RabbitMQ and pull messages as they arrive.

Producer sends to Exchange, then Queue, then Consumer services
Producer sends to Exchange, then Queue, then Consumer services

How is this different from HTTP?

HTTP (REST) RabbitMQ
Timing Sync – wait for the other side Async – put it on a queue; work happens later
Coupling Caller must know the other service URL Caller only needs the event or queue name
Failure Peer down -> immediate error Messages sit in the queue until consumers recover
Good fit Reads, "is this payment approved now?" Email, push, search sync, audit logs

You use both. All-MQ or all-REST is rare. In practice: HTTP when you need an answer now, MQ when the work can wait.

HTTP sync waits on the caller; RabbitMQ lets the API return fast while workers process later
HTTP sync waits on the caller; RabbitMQ lets the API return fast while workers process later

Two patterns in MSA

1) Task queue – "Do this job." Several consumers compete for one queue (thumbnails, PDF, email send).

2) Events (pub/sub) – "This happened." One publish, many services each get their own queue (OrderCreated -> stock, notify, analytics).

The order walkthrough below is pattern 2. For pattern 1, remember: workers share one queue and divide the work.

Order example

  1. User hits the order API -> order saved in the DB.
  2. Order service publishes to RabbitMQ:
{
  "event": "OrderCreated",
  "orderId": "ord-123",
  "userId": "u1",
  "amount": 9900
}
  1. Inventory service – consumes its queue and decreases stock.
  2. Notification service – consumes its queue and sends "order received" email/push.
  3. Analytics / logging (optional) – same event, separate queue.
  4. The order API returns 201 after step 2 – the user does not wait for email to finish.

The order service does not need inventory or notification URLs. It needs the exchange name and message shape. That is loose coupling in MSA.

OrderCreated published once and consumed by inventory, notification, and analytics queues
OrderCreated published once and consumed by inventory, notification, and analytics queues

Exchange types (names to know)

Type One line Example
Direct Exact routing key match order.created -> one post-order queue
Fanout Copy to every bound queue UserRegistered -> email, points, logs
Topic Patterns like order.* Domain event hub

Event hubs often use Topic or Fanout. Simple job queues often use Direct plus a dedicated queue.

Direct, Fanout, and Topic exchange routing patterns
Direct, Fanout, and Topic exchange routing patterns

Ack, DLQ, and idempotency

  • Ack (acknowledge) – consumer tells the broker "done"; then the message leaves the queue.
  • At-least-once – the same message may arrive twice after retries -> design for idempotency (same orderId applied once).
  • DLQ (Dead Letter Queue) – messages that fail repeatedly are isolated for manual review.
  • Prefetch – how many messages a consumer takes at once (avoid overloading one worker).
Ack removes the message; failures retry then land in a dead letter queue
Ack removes the message; failures retry then land in a dead letter queue

How it looks in Spring Boot

Producer:

rabbitTemplate.convertAndSend("orders.topic", "order.created", event);

Consumer:

@RabbitListener(queues = "inventory.order-created")
public void onOrderCreated(OrderCreatedEvent event) {
    inventoryService.decrease(event.orderId());
}

Local RabbitMQ is often one Docker command:

docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
  • 5672 – AMQP for apps
  • 15672 – management UI (inspect queues in the browser)

RabbitMQ vs Kafka (one line)

RabbitMQ Kafka
Role Business messages and job queues Event streams / logs
Messages Usually removed after ack Kept on topics; can replay
Fit Notify, post-order work, mid-size MSA High volume, analytics, replay

For many teams starting with "after order: notify + inventory," RabbitMQ is a common first choice.

When RabbitMQ is a good fit

  • Return the API response fast; do heavy work in the background.
  • Several services must each react to the same event.
  • You want more throughput by adding consumer instances.
  • A peer can be briefly down and work can finish later.

If you need an answer right now (payment approval), use HTTP or gRPC.

Out of scope here

  • RabbitMQ cluster HA and quorum queues
  • Full Saga / compensation design
  • Migrating to Kafka

A natural follow-up post: Spring Boot + RabbitMQ with local Docker, end to end.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *