Event-Driven Architecture: Decoupling Your Monolith
"Everything is an event." When a user signs up, that's an event. When they pay, that's an event. Treating your system as a stream of events changes how you build software.
Sync vs Async Communication
Synchronous (Request/Response)
User -> Order Service -> Inventory Service -> Payment Service
Problem: If Inventory Service is down, the whole chain fails. Latency adds up.
Asynchronous (Event-Driven)
User -> Order Service -> (Publishes "OrderPlaced") -> Inventory Service (Listens) -> Payment Service (Listens) -> Email Service (Listens)
Benefit: Order Service replies "Received" immediately. Other services process at their own pace.
Key Concepts
Producer
The service that creates the event. (e.g., Order Service)
Consumer
The service that reacts to the event. (e.g., Shipping Service)
Message Broker
The post office. Delivers messages. common choices:
- RabbitMQ: Perfect for tasks/queues. Complex routing logic.
- Kafka: Perfect for streams/data. High throughput, persistent log.
- AWS SQS/SNS: Managed simple queues and pub/sub.
Implementation Example (Node.js + RabbitMQ)
// Producer (Order Service)
async function placeOrder(order) {
await db.orders.create(order);
// Publish event
channel.publish('orders', 'order.created', Buffer.from(JSON.stringify(order)));
}
// Consumer (Email Service)
channel.consume('order_created_queue', (msg) => {
const order = JSON.parse(msg.content.toString());
sendWelcomeEmail(order.userEmail);
channel.ack(msg); // Acknowledge receipt
});
The Data Consistency Challenge
In sync systems, you use database transactions. In async, you have Eventual Consistency.
What if "Order Created" succeeds but "Payment Processed" fails? You can't rollback the order easily.
Solution: The Saga Pattern
- Order Service creates order (Pending)
- Payment Service processes payment -> Publishes "PaymentSuccess"
- Order Service listens -> Updates order to "Confirmed"
- OR Payment fails -> Publishes "PaymentFailed"
- Order Service listens -> Updates order to "Cancelled"
When to Use It
Good for:
- High throughput systems
- Decoupling complex workflows
- Analytics and logging
- "Fire and forget" tasks (Emails, Notifications)
Avoid for:
- Simple CRUD apps
- Immediate consistency requirements (e.g., reading your own write instantly)
- Small teams (adds infrastructure complexity)
Start with a simple message queue like Redis or SQS before jumping to Kafka clusters.