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

How to Undo Almost Anything in Git Without Panicking

Git is one of the most forgiving version control systems ever built — but only if you know where to look. Most "I ruined everything" moments are recoverable in under five minutes.

Read more

The Async Remote Contractor Model That Dubai Tech Startups Are Quietly Adopting

The startups shipping fastest in Dubai don't have the biggest engineering teams. They have the best documentation.

Read more

What a Spring Controller Should and Shouldn't Do — A Practical Boundary Guide

Spring controllers accumulate logic because they're the most visible layer and the easiest place to add code. The result is controllers that are hard to test, hard to reuse, and hard to change. Here is a clear boundary that scales.

Read more

How to Politely Push Back on Unreasonable Demands

Clients can sometimes ask for the impossible—or the unreasonable. Here’s how to set boundaries without burning bridges.

Read more