The Problem
In a microservices architecture, one event often needs to trigger multiple independent operations.
Take an e-commerce application. When an order is placed, we might need to:
- Send an email confirmation.
- Send an SMS notification.
- Update analytics.
- Notify the shipping service.
A naive implementation might look like this:
Order Service
|
v
Email Service
|
v
SMS Service
|
v
Analytics Service
|
v
Shipping Service
Each service is called one after another.
Now what if the Email Service is slow or unavailable? The entire flow might get delayed, even though the other services could work independently.
How do we decouple these operations and process them independently?
Option A: Synchronous Calls
The Order Service directly calls every downstream service.
Order Service
/ | \
v v v
Email SMS Analytics
- The Order Service needs to know about every downstream service.
- Slow services increase the overall response time.
- Failures can affect the main request.
- Adding new consumers requires changes to the producer.
It works, but it creates tight coupling.
Option B: Fan-Out Pattern
Instead of calling every service directly, publish a single event to a message broker.
The broker distributes the event to multiple independent consumers.
Order Service
|
v
Message Broker
|
+------------+------------+
| | |
v v v
Email Service SMS Service Analytics
For example, when an order is placed, the Order Service publishes:
{
"event": "OrderCreated",
"order_id": "ORD-123",
"user_id": "USER-456",
"amount": 2500
}
Each consumer receives the event and processes it independently.
- Email Service sends the confirmation.
- SMS Service sends an SMS.
- Analytics Service updates its metrics.
- Shipping Service starts the delivery workflow.
Now, the Order Service doesn't need to know who consumes the event.
That's Fan-Out: One event, multiple independent consumers.
How Does It Work?
A typical implementation looks like this:
- The producer publishes an event to a message broker.
- The broker distributes a copy of the event to each subscriber.
- Each consumer processes its message independently.
- Consumers acknowledge successful processing.
- Failed messages can be retried or moved to a Dead Letter Queue (DLQ).
Trade-offs
Fan-Out improves decoupling and scalability, but it comes with some considerations:
- Eventual Consistency: Consumers may process the event at different times.
- Duplicate Delivery: Consumers should be idempotent to handle duplicate messages safely.
- Failure Handling: Retries and Dead Letter Queues are needed for failed messages.
- Message Durability: The broker must persist messages to avoid losing events.
Fan-Out distributes work; it does not guarantee that every consumer completes its operation successfully.
Where Is It Used?
Some common use cases:
- Notification systems (Email, SMS, Push).
- Order processing and fulfillment.
- Analytics and event tracking.
- Audit logging.
- Real-time data processing.
Common implementations include Kafka (multiple consumer groups), RabbitMQ (Fanout Exchange), and cloud messaging services.
TL;DR
Need to notify multiple services about one event without tightly coupling them? Use Fan-Out to distribute the event and let each consumer process it independently.
NOTE: What if a step in a business transaction fails?
Fan-Out handles event distribution, but it doesn't manage transaction consistency. When multiple services participate in a single business transaction, the Saga Pattern can help coordinate the workflow and execute compensating transactions when a step fails.
Top comments (0)