Skip to content

Event-Driven Architecture — Events, Message Buses (2026)

DodaTech Updated 2026-06-20 6 min read

In this tutorial, you'll learn event-driven architecture — how decoupled services communicate through events rather than direct requests, using message brokers like Kafka and RabbitMQ. Why does this matter? Synchronous request-response coupling creates chains of failures — if one service is down, every caller fails. Events break those chains, enabling resilient, scalable, and auditable systems. Real-world use: every major tech company (Uber, Netflix, LinkedIn) runs on event-driven architectures for critical workflows like order processing, fraud detection, and real-time analytics.

What Is Event-Driven Architecture?

Event-driven architecture (EDA) is a pattern where producers emit events that consumers react to, without the producer knowing who (if anyone) will consume them. Events are immutable records of something that happened — OrderCreated, PaymentProcessed, UserRegistered. A message broker (Kafka, RabbitMQ, AWS SQS/SNS) mediates the communication, providing durability, routing, and delivery guarantees.

graph LR
    subgraph Producers
        P1[Order Service]
        P2[Payment Service]
        P3[User Service]
    end
    
    subgraph Broker[Message Broker]
        T1[Topic: orders]
        T2[Topic: payments]
        T3[Topic: users]
    end
    
    subgraph Consumers
        C1[Inventory Service]
        C2[Notification Service]
        C3[Analytics Service]
        C4[Audit Logger]
    end
    
    P1 -->|OrderCreated| T1
    P2 -->|PaymentProcessed| T2
    P3 -->|UserRegistered| T3
    T1 --> C1
    T1 --> C2
    T2 --> C3
    T3 --> C4
    
    style Producers fill:#4A90D9,color:#fff
    style Broker fill:#E67E22,color:#fff
    style Consumers fill:#2ECC71,color:#fff

How Event-Driven Architecture Works

Event Types

Three distinct event types:

  1. Commands — a directive: "do this" (e.g., ReserveInventory). Always targets a specific consumer.
  2. Events — a notification: "this happened" (e.g., OrderCreated). Zero or many consumers can react.
  3. Queries — a request for data (less common in EDA, usually handled via synchronous APIs).

Message Brokers

Apache Kafka — durable, high-throughput, ordered partitions. Best for event streaming, audit logs, and replay scenarios:

from kafka import KafkaProducer, KafkaConsumer
import json

# Producer
producer = KafkaProducer(bootstrap_servers="localhost:9092")
event = {"type": "OrderCreated", "order_id": "123", "total": 99.99}
producer.send("orders", json.dumps(event).encode())
producer.flush()

# Consumer
consumer = KafkaConsumer("orders", bootstrap_servers="localhost:9092")
for message in consumer:
    event = json.loads(message.value.decode())
    print(f"Processing: {event['type']}{event['order_id']}")
    if event["type"] == "OrderCreated":
        reserve_inventory(event["order_id"])

RabbitMQ — smart broker with flexible routing (direct, topic, fanout exchanges). Best for task queues and routing scenarios:

import pika

# Producer
connection = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
channel = connection.channel()
channel.exchange_declare(exchange="orders", exchange_type="topic")
channel.basic_publish(
    exchange="orders",
    routing_key="order.created",
    body=json.dumps({"order_id": "123"})
)

# Consumer
def callback(ch, method, properties, body):
    event = json.loads(body)
    print(f"Received: {event['order_id']}")
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_consume(queue="inventory_queue", on_message_callback=callback)
channel.start_consuming()

Idempotency

Consumers must handle duplicate events safely. A consumer might receive the same event twice if the broker re-delivers. Use idempotency keys:

class OrderConsumer:
    def __init__(self):
        self._processed = set()  # Track processed event IDs
    
    def handle_order_created(self, event: dict) -> None:
        event_id = event["id"]
        if event_id in self._processed:
            return  # Already processed — skip
        
        # Process the event
        inventory.reserve(event["order_id"])
        self._processed.add(event_id)

Ordering Guarantees

Kafka guarantees ordering within a partition. RabbitMQ guarantees ordering within a queue. For most systems, per-entity ordering (all events for order #123 in order) is sufficient and achievable via partition keys:

# Kafka: partition by order_id ensures ordering per order
producer.send("orders", key=b"order_123", value=event_bytes)

Data Flow

sequenceDiagram
    participant OS as Order Service
    participant K as Kafka
    participant IS as Inventory Service
    participant NS as Notification Service
    
    OS->>K: Emit OrderCreated(order_123)
    K-->>IS: Deliver OrderCreated
    K-->>NS: Deliver OrderCreated
    IS->>IS: Reserve inventory
    IS->>K: Emit InventoryReserved(order_123)
    NS->>NS: Send confirmation email

Real-World Examples

Uber

Uber's dispatch system processes ride events (ride requested, driver assigned, trip started, trip completed) through Kafka. Each microservice reacts to relevant events — payments processes on trip completion, ratings triggers after payment.

Netflix

Netflix's event processing pipeline handles billions of events daily — user interactions, playback events, infrastructure changes. Events trigger analytics, personalization, and alerting systems.

LinkedIn

LinkedIn's "citrus" event bus processes feed updates, notifications, and analytics. Kafka originated at LinkedIn to handle this event-driven architecture.

Pros and Cons

Pros Cons
Loose coupling — producers don't know consumers Eventual consistency — no ACID guarantees
Scalability — consumers scale independently Debugging complexity — hard to trace event flows
Resilience — consumers can fail without affecting producers Duplicate events — must handle idempotently
Audit trail — events are immutable logs Schema evolution — versioning events is tricky
Real-time — react to events as they happen Ordering challenges — guaranteed ordering is expensive

When to Use Event-Driven Architecture

Use EDA when:

  • Decoupling — services should not know about each other
  • Multiple consumers — one event triggers many reactions (e.g., order → inventory + shipping + notifications)
  • Audit compliance — every state change must be recorded
  • Streaming data — real-time analytics, monitoring, fraud detection

Skip EDA for simple CRUD apps, request-response workflows where the caller needs an immediate answer, or when eventual consistency is unacceptable.

FAQ

What is the difference between Kafka and RabbitMQ?

Kafka is optimized for high-throughput event streaming with replay capability — events persist on disk and consumers can re-read them. RabbitMQ is optimized for flexible routing and task queues with immediate consumption. Kafka is like a database of events; RabbitMQ is like a postal service.

How do you handle event schema evolution?

Use a schema registry (like Confluent Schema Registry or Apicurio) with Avro, Protobuf, or JSON Schema. Each event has a schema ID. Producers and consumers reference the registry. Backward-compatible changes (adding optional fields) don't break consumers.

What is exactly-once processing?

Exactly-once processing combines idempotent producers, transactional delivery, and idempotent consumers. Most production systems settle for "at least once" with idempotent consumers — it's simpler and often sufficient.

How do you test event-driven systems?

Test producers and consumers independently with mock brokers. Use contract testing (Pact) to verify event formats. Use integration tests with a real broker in Docker. For resilience, use failure injection (chaos engineering) to test consumer recovery.

What is event sourcing?

Event Sourcing stores state as a sequence of events rather than the current state. It's a related pattern — events are the source of truth, and the current state is derived by replaying them. Event sourcing is often combined with event-driven architecture

Practice Questions

  1. What is the difference between a command and an event?

  2. How does Kafka guarantee message ordering, and within what scope?

  3. What is idempotency, and why is it critical in event-driven systems?

  4. Compare Kafka and RabbitMQ — when would you choose each?

  5. What is eventual consistency, and how does it affect the consumer experience?

Challenge

Design an event-driven system for an e-commerce checkout flow. Define the events (at least 5), the producer service, and the consumer services. Implement a simple Kafka producer that emits OrderCreated and two consumers — one that reserves inventory, one that sends a confirmation.

Real-World Task

Choose a synchronous API in your project that could become event-driven. Identify the producer, the event, and potential consumers. Implement the event emission on the producer side. Create one consumer that handles the event asynchronously. Measure the latency difference between the synchronous and asynchronous paths.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro