Event-Driven Architecture — Reactive Microservices Communication
In this tutorial, you will learn about Event. We cover key concepts, practical examples, and best practices to help you master this topic.
Event-driven architecture uses events as the primary mechanism for communication between Microservices, where services produce and consume events to react to state changes asynchronously.
What You'll Learn
By the end of this lesson you will understand event-driven architecture principles, implement event producers and consumers, design event schemas, handle event ordering and deduplication, and build reactive microservices that respond to system changes.
Why It Matters
Event-driven architecture decouples services more completely than Message Queues. Services emit events without knowing or caring which other services consume them. This enables true independence: services can be added, removed, or replaced without affecting others.
Real-World Use
DodaZIP's entire backend is event-driven. When a file is uploaded, the upload service emits a FileUploaded event. The virus scanner, compression service, thumbnail generator, and backup service each listen for this event independently. Adding a new service requires zero changes to existing services.
flowchart LR
A[Service A] -->|Emits Event| B[Event Bus]
B --> C[Service B]
B --> D[Service C]
B --> E[Service D]
C -->|Emits Event| B
subgraph Event Store
F[Event Log]
end
B --- F
style B fill:#2d3748,color:#fff
Event-Driven Principles
Core concepts of event-driven architecture.
# eda_principles.py
# Event-driven architecture principles
def eda_principles():
print("Event-Driven Architecture Principles")
print("=" * 40)
print()
principles = [
{
"principle": "Event as Fact",
"desc": "Events represent things that have happened. They are immutable facts, not commands.",
"example": "OrderPlaced (past tense), not PlaceOrder (command)"
},
{
"principle": "Publish Once, Consume Many",
"desc": "One event can be consumed by multiple services independently.",
"example": "UserRegistered event consumed by email, analytics, and CRM services"
},
{
"principle": "No Direct Coupling",
"desc": "Producers do not know consumers. Events go to a broker, not to specific services.",
"example": "Upload service emits FileUploaded without knowing who listens"
},
{
"principle": "Eventual Consistency",
"desc": "Services update their own data stores in response to events. Consistency happens over time.",
"example": "Order service confirms order first, then emits event. Inventory updates asynchronously."
},
{
"principle": "Event Schema Evolution",
"desc": "Events must be versioned to allow schema changes without breaking consumers.",
"example": "UserRegistered.v1, UserRegistered.v2 with additional fields"
},
]
for p in principles:
print(f"{p['principle']:30s}")
print(f" {p['desc']}")
print(f" Example: {p['example']}")
print()
eda_principles()
Implementing Events
Designing and publishing events.
# event_implementation.py
# Event producer and consumer
def event_implementation():
print("Event Implementation")
print("=" * 40)
print()
event_schema = """
# Event schema design
{
"event": {
"id": "evt_abc123def456", # Unique event ID
"type": "order.placed", # Hierarchical event type
"version": 1, # Schema version
"timestamp": "2026-06-28T10:30:00Z", # ISO 8601
"producer": "order-service", # Source service
"trace_id": "trace_xyz789", # Distributed tracing ID
"data": { # Event-specific payload
"order_id": "ORD-12345",
"customer_id": "CUST-678",
"total": 59.99,
"items": [
{"product_id": "PROD-1", "quantity": 2}
]
}
}
}
"""
print("Event Schema:")
print(event_schema)
producer_code = """
import json, uuid, datetime
def publish_event(event_bus, event_type, data, trace_id):
event = {
"id": str(uuid.uuid4()),
"type": event_type,
"version": 1,
"timestamp": datetime.datetime.utcnow().isoformat() + "Z",
"producer": "order-service",
"trace_id": trace_id,
"data": data
}
# Publish to event bus (Kafka/RabbitMQ/etc)
event_bus.publish("order.events", json.dumps(event))
print(f"Published: {event_type} ({event['id']})")
return event
# Usage
publish_event(
kafka_producer,
"order.placed",
{"order_id": "ORD-12345", "total": 59.99},
"trace_xyz789"
)
"""
print("Producer:")
print(producer_code)
event_implementation()
Event Consumer Patterns
Different ways services consume events.
# consumer_patterns.py
# Event consumer implementation
def consumer_patterns():
print("Event Consumer Patterns")
print("=" * 40)
print()
consumer_code = """
import json
def handle_event(event_json):
event = json.loads(event_json)
event_type = event["type"]
# Route by event type
handlers = {
"order.placed": handle_order_placed,
"order.shipped": handle_order_shipped,
"order.cancelled": handle_order_cancelled,
"payment.received": handle_payment_received,
}
handler = handlers.get(event_type)
if handler:
try:
handler(event["data"], event["trace_id"])
print(f"Processed: {event_type}")
except Exception as e:
print(f"Failed to process {event_type}: {e}")
# Re-raise to trigger retry/DLQ
raise
else:
print(f"Unknown event type: {event_type}")
def handle_order_placed(data, trace_id):
print(f"Order placed: {data['order_id']}, amount: {data['total']}")
# Update local inventory count
# Send confirmation email
# Notify shipping service
def handle_order_shipped(data, trace_id):
print(f"Order shipped: {data['order_id']}, carrier: {data['carrier']}")
# Kafka consumer loop
def consume_loop(consumer, topic):
consumer.subscribe([topic])
for message in consumer:
handle_event(message.value)
"""
print("Consumer with event routing:")
print(consumer_code)
consumer_patterns()
Event Ordering and Deduplication
Handling out-of-order and duplicate events.
# ordering_dedup.py
# Event ordering and deduplication
def ordering_dedup():
print("Event Ordering and Deduplication")
print("=" * 40)
print()
dedup_code = """
import redis
class Deduplicator:
"""Idempotent event processing."""
def __init__(self, redis_client):
self.redis = redis_client
self.processed_window = 300 # 5 minutes
def is_duplicate(self, event_id):
"""Check if event already processed."""
return self.redis.exists(f"processed:{event_id}")
def mark_processed(self, event_id):
"""Mark event as processed with TTL."""
self.redis.setex(
f"processed:{event_id}",
self.processed_window,
"1"
)
def process_once(self, event):
if self.is_duplicate(event["id"]):
print(f"Skipping duplicate: {event['id']}")
return False
# Process event
self.mark_processed(event["id"])
return True
# Usage
dedup = Deduplicator(redis_client)
if dedup.process_once(event):
# Do the actual work
update_inventory(event["data"])
"""
print("Deduplication:")
print(dedup_code)
print()
ordering_code = """
# Handling out-of-order events
def handle_order_event(event):
# Store event in local event store
store.save_event(event)
# Check if we can apply it
sequence_number = event["data"]["sequence"]
last_applied = store.get_last_sequence()
if sequence_number == last_applied + 1:
# Events are in order, apply directly
apply_event(event)
elif sequence_number > last_applied + 1:
# Gap detected, wait for missing events
print(f"Gap detected. Expected {last_applied + 1}, got {sequence_number}")
# Missing events will be applied when they arrive
else:
# Duplicate, skip
print(f"Duplicate event: {event['id']}")
"""
print("Ordering:")
print(ordering_code)
ordering_dedup()
Common Mistakes
Using events as commands: Events describe past facts (OrderPlaced), not future intentions (PlaceOrder). Commands should be sent via request-response or command queues.
No event versioning: Without schema versioning, changing an event field breaks all consumers. Always include a version field and maintain backward compatibility.
Assuming FIFO ordering: Most event brokers do not guarantee global ordering. Design consumers to handle out-of-order events using sequence numbers.
Not handling duplicates: Network issues cause duplicate event delivery. All event consumers must be idempotent using event ID deduplication.
Cascading event storms: One event triggering another service that emits another event can create infinite loops. Implement event TTL and cycle detection.
Practice Questions
What distinguishes event-driven from message queue communication? Events are published facts consumed by any interested service. Message queues deliver tasks to specific workers.
Why must events be immutable? Events represent things that have happened. Past facts cannot be changed. If data changes, emit a new event.
What is an event schema version? A number in the event payload that indicates the schema version, allowing consumers to handle different formats.
How do you prevent duplicate event processing? Store processed event IDs with TTL in a fast data store (Redis) and check before processing.
Challenge: Design an event-driven system for a food delivery platform. Define the events (OrderPlaced, RestaurantAccepted, DriverAssigned, Delivered), the services that produce and consume each, and how you handle out-of-order events.
FAQ
Mini Project
Design an event-driven architecture for a blog platform. Define events for PostCreated, PostPublished, CommentAdded, UserFollowed. Implement a producer (blog service), and two consumers (notification service sends emails, analytics service tracks engagement). Include event schema, deduplication, and error handling.
def blog_event_design():
print("Blog Platform - Event-Driven Design")
print("=" * 40)
print()
print("Events:")
print(" PostCreated - Blog service emits when author writes")
print(" PostPublished - Blog service emits on publication")
print(" CommentAdded - Blog service emits on new comment")
print(" UserFollowed - User service emits when follow happens")
print()
print("Services & Subscriptions:")
print()
print(" Notification Service:")
print(" - PostPublished -> Email followers")
print(" - CommentAdded -> Email post author")
print(" - UserFollowed -> Email followed user")
print()
print(" Analytics Service:")
print(" - PostCreated -> Track writing frequency")
print(" - PostPublished -> Track publish metrics")
print(" - CommentAdded -> Track engagement")
print()
print(" Search Index Service:")
print(" - PostPublished -> Update search index")
print(" - PostCreated -> Index draft")
print()
print("No service knows about other services.")
blog_event_design()
What's Next
Next: Event Sourcing for event-sourced Microservices Patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro