· Updated 2026-08-06

Event-Driven Architecture: When to Use It and How to Get It Right (2026)

When a service calls another service synchronously, it accepts two risks: the downstream service's latency becomes the caller's latency, and the downstream service's failure becomes the caller's failure.

For many interactions, this coupling is fine. For others — high-throughput processing, fan-out to multiple subscribers, async workloads that do not need an immediate response — it creates brittleness that scales poorly.

Event-driven architecture removes that coupling. Producers emit events; consumers react at their own pace. Neither knows about the other. The trade-off is that you introduce a new class of problems: at-least-once delivery, consumer lag, event ordering, and the operational complexity of a message broker between every interaction.

This guide explains when EDA is worth that trade-off, the patterns you need to understand, how to choose a broker, and the failure modes you need to plan for.


When Event-Driven Architecture Is the Right Choice

EDA solves specific problems. If those problems exist in your system, EDA is a strong fit. If they do not, it adds complexity without payoff.

Use EDA when:

Multiple services need to react to the same event. An order placement might need to trigger: inventory reservation, payment processing, notification sending, analytics ingestion, and fraud scoring. In a synchronous system, the order service calls all five — coupling it to five downstream services, any of which can slow or break the order flow. With pub/sub, the order service emits one event and all five consumers react independently.

Downstream processing can be async. Invoice generation, email sending, report building, thumbnail creation — none of these need to complete before the user gets a response. Queuing them as events lets the core transaction complete fast and the side effects process at their own rate.

You need to handle traffic spikes. During a flash sale, order volume might spike 20× for 10 minutes. A synchronous system either fails under that load or is sized for peak (expensive). A message broker absorbs the spike, holding events until consumers process them at a sustainable rate.

You need event replay. A new analytics service needs historical data. A bug in a consumer produced incorrect records that need to be corrected by replaying events. Kafka retains events for a configurable period — new consumers can start from any point in the log.

Do not use EDA when:

  • The interaction is inherently synchronous — a user requesting their account balance needs an answer now
  • You have a simple, small system where the operational overhead of a broker outweighs the coupling cost
  • You need strong transactional consistency across multiple services synchronously — EDA produces eventual consistency, not ACID transactions

Core Patterns

Pub/Sub (Publish-Subscribe)

The foundation of EDA. A producer publishes events to a topic; multiple independent consumer groups subscribe and each receives every event.

Order Service → [order.placed] → ┬→ Inventory Service
                                  ├→ Payment Service
                                  ├→ Notification Service
                                  └→ Analytics Service

Each consumer processes at its own pace and can fail or redeploy independently of others.

Event Sourcing

Instead of storing current state, store a sequence of events and derive state by replaying them.

# Traditional: store current state
orders table: {id: 1, status: "shipped", amount: 150}

# Event sourcing: store events, derive state
events: [
  {type: "OrderPlaced",   orderId: 1, amount: 150},
  {type: "PaymentReceived", orderId: 1, amount: 150},
  {type: "OrderShipped",  orderId: 1, trackingId: "UPS123"}
]
# Current state = replay of all events

Benefits: complete audit trail, ability to reconstruct state at any point in time, natural fit for event-driven systems.

Costs: significant implementation complexity, eventual consistency of read models, challenges with schema evolution.

CQRS (Command Query Responsibility Segregation)

Separate the write model (commands) from the read model (queries). Projectors consume events and maintain read-optimised views:

Command → Write Model → Events → Event Store
                                      ↓
                               Projectors maintain
                               Read Models (SQL, Elasticsearch, Redis)
                                      ↓
                               Queries hit Read Model

This allows the read model to be shaped exactly for query requirements without coupling it to write-model structure. It is most valuable in complex domains; it is overkill for simple CRUD services.


Broker Selection

Broker Throughput Retention Fan-out Managed option Best for
Apache Kafka Very high (millions/sec) Configurable (days → forever) Yes (consumer groups) AWS MSK, Confluent Cloud High throughput, replay, multi-consumer
AWS SQS + SNS High 14 days max Yes (via SNS) Fully managed Simpler AWS-native fan-out
RabbitMQ Medium Short (consume and delete) Yes (exchanges) CloudAMQP Complex routing, existing AMQP expertise
AWS EventBridge Medium 24h archive, replay up to 90 days Yes Fully managed Serverless, AWS service integration
Redis Streams Medium Configurable Yes (consumer groups) Redis Cloud Low-latency, already using Redis

Decision guide:

  • High throughput + event replay needed → Kafka (self-hosted or managed)
  • AWS-native, simpler operations, moderate throughput → SQS + SNS
  • Complex routing rules, AMQP ecosystem → RabbitMQ
  • Serverless AWS workloads → EventBridge
  • Already running Redis, low latency priority → Redis Streams

Handling Failures

Event-driven systems fail differently from synchronous systems. The failures are more distributed and harder to observe.

At-least-once delivery

Most brokers guarantee at-least-once delivery — an event will be delivered, but may be delivered more than once (on retries, network issues, or consumer restarts). This is not optional behaviour; it is by design for reliability.

Consequence: every consumer must be idempotent — processing the same event twice must produce the same result.

# Idempotency via event ID tracking
def handle_payment_received(event):
    if ProcessedEvent.exists(event.id):
        return  # already processed, skip
    
    with transaction():
        # Process the payment
        order.mark_as_paid()
        # Record processing
        ProcessedEvent.create(event.id)

Dead-letter queues

Events that fail processing after N retries must go somewhere. A dead-letter queue (DLQ) holds them for inspection and manual replay:

Consumer → fails 3 times → Dead-Letter Queue → Alert → Human review → Replay

A DLQ that is not monitored is not a safety net — it is a silent data loss mechanism. Monitor DLQ depth and alert on any message entering the DLQ.

Consumer lag monitoring

# Kafka consumer lag monitoring
kafka-consumer-groups.sh \
  --bootstrap-server localhost:9092 \
  --describe \
  --group order-processing-group

# Output: lag per partition
# Topic: order.placed, Partition: 0, Lag: 15,342

A consumer that cannot keep up with the producer will eventually fall so far behind that it cannot recover without intervention. Monitor lag continuously and alert before it becomes critical.


Event Schema Design

Events are an API — they must be versioned carefully because consumers depend on them.

{
  "specversion": "1.0",
  "type": "com.iqcrafter.order.placed",
  "id": "a1b2c3d4-...",
  "time": "2026-08-06T10:30:00Z",
  "source": "/order-service",
  "schemaversion": "2",
  "data": {
    "orderId": "ORD-12345",
    "customerId": "CUST-789",
    "totalAmount": 149.99,
    "currency": "USD",
    "items": [...]
  }
}

Schema evolution rules:

  • Adding optional fields: safe — existing consumers ignore unknown fields
  • Removing fields: breaking — consumers that depend on the field will fail
  • Renaming fields: breaking — treat as remove + add (use a transitional period where both names are present)

Use a schema registry and enforce compatibility checks before new event versions are published.


Common Mistakes

Mistake Consequence Fix
Non-idempotent consumers Duplicate processing corrupts data Track processed event IDs; design operations to be naturally idempotent
No DLQ Failed events are silently lost Configure DLQ for every consumer; monitor and alert on DLQ depth
Events containing commands Tight coupling re-introduced through events Events record facts ('OrderPlaced'), not instructions ('ProcessOrder')
Synchronous calls inside event handlers Couples consumer to downstream service response time Emit a new event instead of making a synchronous call from a consumer
No schema versioning Breaking schema changes break all consumers Use a schema registry; version all event types; additive changes only
No consumer lag alerting Consumers fall behind silently until it is a crisis Monitor lag continuously; alert at threshold

Event-driven architecture is a powerful pattern that unlocks async processing, decoupled scaling, and reliable fan-out — but it introduces a class of distributed systems complexity that synchronous architectures do not have. The right time to introduce it is when the coupling cost of synchronous communication is measurably hurting your system.

For help designing an event-driven architecture or evaluating whether EDA is appropriate for your current system, see our Enterprise Software Development capabilities or get in touch. Our guide on Microservices vs Monolith covers the broader architecture context in which EDA most commonly applies.

Need Expert Guidance?

Planning custom software for your business?

Book a free consultation with our team to discuss architecture, product strategy, and the right build approach for your goals.

Book Free Consultation