DEV Community

Nadeem Ur-Rehman
Nadeem Ur-Rehman

Posted on

Your Event Bus Is a Distributed Monolith in Disguise

Watch the 40-second version on YouTube

Event-driven architecture looks brilliant on the whiteboard. Then Friday arrives and your "decoupled" services form a single failure-shaped organism.

Everyone loves the diagram: services publishing events, consumers reacting, everything loosely coupled, arrows everywhere. Then one producer double-publishes an OrderCancelled event, and three services react by cancelling, un-cancelling, and re-cancelling the same order until your support queue catches fire.

Here is the part the conference talks skip: event-driven does not remove coupling. It moves it from compile time to runtime, where it is harder to see and impossible to grep. A REST call fails loudly in your face. An event fails quietly, hours later, in a consumer you forgot existed, written by a team that left the company in 2023.

The non-obvious angle: event-driven systems are not decoupled. They are invisibly ordered. Every consumer secretly depends on the order, timing, and exactly-once delivery of events the bus never actually promised. You get the worst of both worlds: distributed-systems problems with monolith-era debugging tools. Your tracing span ends at the publish call. After that, good luck.

The event-bus checklist

Before you reach for the bus, answer yes to all four:

  1. Can every consumer handle this event twice without corrupting state? (Idempotency, not hope.)
  2. Can every consumer handle it out of order without corrupting state? (Sequencing, not hope.)
  3. If the producer is dead, does the system degrade gracefully or just stop? (Design the dead-bus scenario on purpose.)
  4. Could you name every consumer of this event right now, from memory? (If not, you have a governance problem, not an architecture.)

If you answered no to #4, you already have a distributed monolith. The bus just made it harder to admit.

One naming rule that saves careers

Events are a fine tool for things that are genuinely facts about the past: payment.settled, user.signed_up. They are a terrible tool for commands in disguise: cancel.the.order. If your event name starts with a verb, it is a command. Admit it, make it a call, and move on.

Use the bus where it shines: real domain facts, consumers that tolerate delay and duplicates, and a producer that keeps working even when nobody is listening. Everywhere else, a boring synchronous call is still undefeated. Fewer arrows on the diagram. Fewer pages at 3 AM.

Top comments (0)