Event Driven Architecture: Powerful Pattern or Distributed Mess

by Arif Ikhsanudin, Backend Developer

When It Works

Event-driven architecture (EDA) is a pattern where system components communicate by producing and consuming events rather than by direct calls. A service publishes OrderPlaced to an event bus. Inventory, billing, and notification services each subscribe to that event and react independently. No service calls another directly.

This pattern genuinely shines in specific conditions: when you have multiple independent consumers for the same state change, when those consumers have different reliability and scaling requirements, and when you want to add new consumers without modifying the producer.

E-commerce checkout is the textbook example: OrderPlaced triggers inventory reservation, payment charging, warehouse notification, customer email, analytics recording — all independently, all able to fail and retry without blocking each other. The producer does not need to know the consumers exist.

When It Becomes a Mess

EDA applied without discipline creates systems where behavior is impossible to trace. A request enters, events cascade through N services in a partially-ordered graph, and the final state emerges from an interaction that no single service fully understands. Debugging requires reconstructing event flows across service logs and distributed trace spans.

Three specific failure patterns:

Event chains. Service A publishes event 1. Service B consumes event 1 and publishes event 2. Service C consumes event 2 and publishes event 3. A failure in step 3 requires understanding the chain from step 1 to debug. The further the chain, the harder the debugging. Long event chains are often a sign that EDA is being used where an orchestrator (a process that explicitly sequences steps) would be clearer.

Implicit ordering dependencies. Events are processed asynchronously and in arbitrary order unless you design explicitly for ordering. A PaymentProcessed event consumed before the corresponding OrderCreated event is an edge case that will occur eventually at scale. Every consumer must handle out-of-order delivery, which means every consumer needs to query state rather than assume state from the event alone.

# Fragile: consumer assumes event arrives in order and trusts event data
def handle_payment_processed(event):
    order = event.data["order"]  # Assume this is current
    send_receipt(order)          # What if order was modified after payment?

# Robust: consumer fetches current state
def handle_payment_processed(event):
    order = db.get_order(event.data["order_id"])  # Fetch current state
    if order.status != "payment_complete":
        # Unexpected state -- handle it
        return
    send_receipt(order)

Schema evolution. An event schema change — renaming a field, changing a type — requires coordinating all consumers simultaneously or maintaining backward compatibility. With two consumers, this is manageable. With fifteen consumers, one of which is an analytics pipeline owned by a different team, this is a coordination problem that can block the producer from evolving.

The Choreography vs Orchestration Decision

EDA as described above is choreography — each service reacts independently, no central coordinator. The alternative is orchestration — a central process (a saga orchestrator, a workflow engine) explicitly sequences the steps and handles failures.

Choreography scales better and is more loosely coupled. Orchestration is easier to reason about, debug, and recover from failures. The choice depends on how complex the process is and how important traceability is.

For simple fan-out (one event, multiple independent consumers), choreography is cleaner. For multi-step processes with compensating transactions (place order → reserve inventory → charge payment → if payment fails, release inventory), orchestration with a tool like Temporal or AWS Step Functions is usually more appropriate than a chain of events with implicit compensation logic.

The Practical Test

Before adopting EDA for a new integration, ask: can I draw the complete event flow — every service that produces or consumes each event, and every state transition — in under 10 minutes? If you cannot, the system is already complex enough that adding more event-driven coupling will make it harder, not easier. Consider whether a simpler direct call or an explicit workflow would be clearer.

EDA is not inherently complex. Systems built with EDA without discipline are.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Offshore vs Nearshore vs Remote — What These Labels Actually Mean for Your Team

Offshore, nearshore, and remote are geography and timezone labels that carry different collaboration costs — understanding those costs in concrete terms is the prerequisite to choosing the model that fits your actual working style.

Read more

How I Balance Writing Code and Leading a Team at the Same Time

Staying technical as a tech lead is not about protecting your identity as a developer — it's about being a credible, useful leader. But doing it well requires real boundaries and deliberate choices.

Read more

Why MVC Is Not Enough for Complex Backend Systems

MVC is great for small apps, but when your backend starts juggling caching, queues, and multiple APIs, it quickly shows its limits.

Read more

Spring Boot API Rate Limiting — rack-attack Equivalent in Java

Rate limiting protects APIs from abuse, enforces fair usage, and prevents accidental runaway clients from taking down infrastructure. Here is how to implement per-user, per-IP, and per-endpoint rate limiting in Spring Boot with Bucket4j and Redis.

Read more