ReviseAlgo Logo

Event-Driven Architecture Patterns

Event-Driven Architecture (EDA) — Core Concepts

Understanding event delivery topologies, temporal decoupling, and design guidelines.

Introduction

In modern distributed systems, services must operate with high autonomy and minimal latency. Event-Driven Architecture (EDA) is an architectural paradigm where software components communicate asynchronously by producing and reacting to Events—immutable notifications of significant changes in state or facts that have occurred in the business domain.

Why It Matters

Unlike traditional request-response architectures (such as REST/gRPC), which force services to be spatially and temporally coupled, EDA isolates components completely. This provides temporal decoupling (producers do not wait for consumers to be online or complete execution) and extensibility (new consumers can be added to ingest event streams without changing any producer code).

Real-World Analogy

Think of EDA like a restaurant ticket wheel. When a waiter places an order, they write the ticket and stick it on the wheel (the event log). The kitchen staff (consumers) read the ticket, prepare the food, and ring a bell. The waiter does not stand in front of the chef waiting for the food to cook (synchronous request-response); instead, they return to the dining room to serve other customers.

How It Works

The lifecycle of event propagation in EDA involves:

  1. Event Publication: A producer performs a state change (e.g., registers a user) and broadcasts a domain event (e.g., UserRegistered) to an event broker.
  2. Log Appendation: The event broker (like Kafka) records the event durably in an append-only, partitioned commit log.
  3. Asynchronous Consumption: Multiple independent consumer groups read the event from partitions at their own pace, processing it for distinct tasks like sending emails, updating analytics, or processing payments.

Internal Architecture

Traditional message brokers (like RabbitMQ) route messages to queues, deleting them immediately after a consumer acknowledges processing. In contrast, Kafka stores events as an immutable sequence of bytes on disk. This design allows Kafka to support Event Replay—consumers can seek backward in time to any offset and reprocess historical streams, ensuring high tolerance for downstream system crashes or logic bugs.

Visual Explanation

Below is an ASCII representation showing how a single event is broadcast asynchronously to multiple distinct consumer services via a partitioned Kafka topic:

                                   ┌───────────────┐
                                   │ Partition 0   │ ──> Consumer A (Email Service)
                                   ├───────────────┤
[Producer] ──> (OrderCreated) ──>  │ Partition 1   │
                                   ├───────────────┤
                                   │ Partition 2   │ ──> Consumer B (Billing Service)
                                   └───────────────┘
            

Practical Example

Here is a Java example demonstrating a standard, structured domain event envelope containing a generic payload, unique identifier, correlation ID, and timestamp metadata:

Common Mistakes

  • Naming events as commands: Naming an event CreateOrder instead of OrderCreated. Commands convey intent to do something and can be rejected. Events represent an immutable fact that has already happened.
  • Building synchronous callbacks inside event handlers: Forcing a consumer to make a blocking HTTP call back to the producer to query details. This breaks the temporal decoupling advantage of EDA.

Quick Quiz

Q1: How does an event differ fundamentally from a command in software architectures?

An event represents a statement of past fact (immutable, historical), whereas a command is an instruction requesting future action (which can fail or be rejected).

Q2: Why does decoupling producers from consumers improve system resilience?

Because if a consumer service goes offline, the producer continues to publish events to the broker without failing. The consumer simply catches up once it recovers.

Scenario-Based Challenge

The Challenge: Transitioning an order pipeline from REST orchestration to Event Choreography

Your checkout system currently orchestrates checkout via sequential REST API calls to Order, Payment, and Inventory services. If the Inventory service encounters high latency, the entire checkout pipeline blocks. How do you design this as an event-driven flow?

Solution Tip: Have the Order service process the checkout command and publish an OrderPlaced event to Kafka. The Payment and Inventory services subscribe to the topic, receive the event, and perform their tasks concurrently, emitting PaymentAuthorized or InventoryReserved events once done.

Debugging Exercise

Your consumer service logs frequent CommitFailedException errors, and Kafka keeps rebalancing your consumer group, shifting tasks from one instance to another.

The Fix: The consumer is taking too long to process a batch of events, exceeding the max.poll.interval.ms configuration. Kafka assumes the consumer has hung and evicts it. Resolve this by increasing max.poll.interval.ms, decreasing max.poll.records, or delegating the heavy processing logic to a separate worker thread pool.

Interview Questions

1. What is the difference between point-to-point messaging queues and publish-subscribe event logs?

Point-to-point queues deliver a message to exactly one consumer and delete it. Pub-sub event logs append events to an immutable file structure, allowing multiple consumer groups to read and replay the same event stream.

2. What are correlation and causation IDs, and why are they crucial in EDA?

Correlation IDs track events spawned by the same user request across services. Causation IDs track the specific parent event that triggered a child event, making distributed tracing possible.

Production Considerations

Keep event sizes small (ideally under 100KB). For large payloads like images, use the Claim-Check pattern: store the payload in S3/blob storage and publish the storage path in the event.