Microservices Event-Driven Design
Event-driven design is a way of building microservices where services communicate by broadcasting what happened rather than calling each other directly. Instead of Service A calling Service B and waiting, Service A announces an event. Any service that cares about that event reacts to it independently.
Events vs Commands
An event is a fact about something that already happened. It describes the past.
A command is a request for something to happen. It describes the future.
COMMAND (imperative, direct)
"Send a welcome email to user 42"
Order Service tells Email Service exactly what to do.
EVENT (factual, broadcast)
"UserRegistered { user_id: 42, email: john@example.com }"
User Service announces what happened.
Email Service, Billing Service, Analytics Service
each decide what to do with this fact.
Events decouple services. The User Service does not know (or care) who listens to its events. It just publishes them.
The Event Bus
An event bus is the system that carries events from publishers to subscribers. Publishers put events on the bus. Subscribers read events from the bus.
EVENT BUS DIAGRAM
=================
[User Service] PUBLISHES
[Order Service] ---> [EVENT BUS] ---> [Email Service] SUBSCRIBES
[Payment Service] ---> [Analytics Service] SUBSCRIBES
---> [Fraud Service] SUBSCRIBES
Each service publishes to one place.
Each service subscribes to what it needs.
No service knows who else is reading the events.
Apache Kafka is the most widely used event bus in production systems. RabbitMQ and AWS SNS/SQS are also popular choices.
A Real Scenario: E-Commerce Order Flow
STEP 1: Customer places order
[Order Service] publishes: OrderPlaced
{
order_id: "ORD-7701",
user_id: "USR-212",
items: ["SKU-001", "SKU-055"],
total: 89.99
}
STEP 2: Multiple services react simultaneously
[Inventory Service] hears OrderPlaced --> reserves items in warehouse
[Payment Service] hears OrderPlaced --> initiates charge
[Analytics Service] hears OrderPlaced --> records order for reporting
STEP 3: Payment Service processes charge, publishes: PaymentSucceeded
{ order_id: "ORD-7701", amount: 89.99 }
STEP 4: More services react
[Order Service] hears PaymentSucceeded --> updates order status to "Confirmed"
[Email Service] hears PaymentSucceeded --> sends order confirmation email
[Loyalty Service]hears PaymentSucceeded --> adds reward points to account
Each service reacts to events in parallel. The total time to complete all background steps is reduced because services do not wait for each other.
Event Schema Design
Events must carry enough information for subscribers to act without calling back to the publisher for more data.
THIN EVENT (bad — forces extra API calls)
{
"event": "OrderPlaced",
"order_id": "ORD-7701"
}
Email Service must call Order Service to get user_id and items.
That defeats the purpose of async communication.
FAT EVENT (good — self-contained)
{
"event": "OrderPlaced",
"order_id": "ORD-7701",
"user_id": "USR-212",
"user_email": "john@example.com",
"items": [{"sku": "SKU-001", "name": "Running Shoes", "qty": 1}],
"total": 89.99,
"timestamp": "2025-03-14T10:23:00Z"
}
Email Service has everything it needs. No extra calls required.
Event Versioning
Events change over time as business needs evolve. Adding a new field to an event is safe — existing consumers ignore fields they do not recognize. Removing or renaming a field breaks existing consumers.
SAFE CHANGE:
v1: { order_id, user_id, total }
v2: { order_id, user_id, total, discount_code } <-- added field, safe
BREAKING CHANGE:
v1: { order_id, user_id, total }
v2: { order_id, customer_id, total } <-- renamed "user_id" to "customer_id"
All consumers reading "user_id" get nothing. BREAKING.
When you need breaking changes, introduce a new event type alongside the old one and migrate consumers gradually.
Event Sourcing
Event Sourcing is a pattern where instead of storing the current state of data, you store every event that ever changed that data. The current state is calculated by replaying all events from the beginning.
TRADITIONAL STATE STORAGE
Account balance: $200
EVENT SOURCING
AccountCreated { balance: 0 }
MoneyDeposited { amount: 500 }
MoneyWithdrawn { amount: 150 }
MoneyDeposited { amount: 100 }
MoneyWithdrawn { amount: 250 }
Replay all events: 0 + 500 - 150 + 100 - 250 = $200
Event Sourcing gives you a complete history of every change — useful for financial systems, audit logs, and debugging. The downside is complexity: replaying thousands of events to compute current state requires optimization techniques like snapshots (saving the current state at checkpoints so you do not replay from the very beginning every time).
Benefits and Trade-offs
Benefits
- Services are loosely coupled — publishers do not know about subscribers.
- New services subscribe to existing events without changing the publisher.
- The system handles spikes well — the event bus absorbs bursts of traffic and services process at their own pace.
- If a service is temporarily offline, its events wait in the queue and get processed when it restarts.
Trade-offs
- Debugging is harder — tracing a request across multiple services takes specialized tooling.
- Eventual consistency means data is not always in sync immediately.
- Designing events correctly from the start requires careful thinking — bad event schemas cause refactoring pain later.
