Jump to content

Event-Driven Architecture: When Not to Use It

In this Article

  1. Key takeaways: EDA is not architectural seasoning
  2. Why smart teams still overuse events
  3. Do not use it when the workflow is really one transaction
  4. Avoid it when nobody owns the full failure path
  5. Do not publish events before the domain has stopped wobbling
  6. Use boring designs until events earn their keep
  7. When I would actually choose event-driven architecture

Key takeaways: EDA is not architectural seasoning

Event-driven architecture is excellent for decoupled reactions. It is not a condiment you sprinkle over every business workflow that has more than a couple of services.

I learned that the expensive way. In one migration, we mandated event-driven communication across all new microservices because it sounded clean: no direct calls, no tight coupling, no old monolith habits. Approximately six months later, developers spent more time writing correlation ID middleware than shipping useful behavior. Our experience showed mean time to recovery rising by around 60% early in adoption.

The practical rule is boring and usually correct: if the business process needs strict ordering, immediate consistency, and easy human debugging, start with a simpler synchronous or transactional design.

Summary: Use events for reactions that can happen later. Do not use them to hide a transaction that the business still expects to behave like one transaction.

The warnings I check first

  • Hidden coupling: consumers still depend on producers, only now through topic names, payload fields, and timing assumptions.
  • Replay complexity: replaying messages can recreate old bugs if handlers are not version-aware.
  • Duplicate handling: every consumer needs idempotency, not just the important-looking ones.
  • Schema drift: a renamed field becomes a negotiation with teams you may not even know exist.
  • Operational debugging cost: a stack trace becomes a scavenger hunt through logs, traces, queues, and dead-letter topics.

A practical decision map for choosing between synchronous calls, transactions, orchestration, and event-driven reactions.

Why smart teams still overuse events

The common question is fair: if events cause so much pain, why do strong engineering teams keep choosing them?

Because the sales pitch is seductive. Loose coupling. Independent deployments. Asynchronous scale. Cleaner boundaries. Nobody says the quiet part during the architecture review: you may not remove complexity. You may only move it somewhere with worse tooling.

Function calls become topics. Stack traces become correlation IDs. Local reasoning becomes archaeology.

In practice, I have seen architecture boards approve a massive broker rollout on the promise of independent deployments, then watch teams broadcast database mutations because nobody had done the domain work. The numbers were ugly: about 85% of published events were essentially CRUD operations rather than true domain facts. UserUpdated. RecordChanged. StatusModified. Those names do not describe a business event. They describe a table twitching.

Member feedback indicates that tooling can make this worse. One group spent in the range of 3-5 months building custom distributed tracing dashboards. Developers mostly ignored them because the dashboards answered infrastructure questions, not product questions. They could show that a message moved. They could not explain whether a customer actually got what they paid for.

The cultural trap

Broker adoption is not domain design.

A broker can move bytes between services. It cannot tell you whether InvoiceApproved is a finance decision, a workflow state, a compliance artifact, or a side effect of somebody clicking the wrong admin button. If the domain language is mushy, events preserve the mush and distribute it.

Note: If your event names expose database operations instead of business facts, you probably built a distributed change-data feed and called it architecture.

Do not use it when the workflow is really one transaction

Some workflows want an immediate answer. Pretending otherwise punishes users.

Payment authorization, inventory reservation, permission checks, quota enforcement, and user-facing confirmation flows often need a clear yes or no. The business may tolerate retries behind the scenes, but the user interface cannot sit there composing a philosophy essay about eventual consistency.

Here is the example I still use in design reviews: a high-contention checkout flow with inventory reservation. We attempted to use the saga pattern for a multi-step checkout and inventory reservation flow. The compensating transactions consistently collided under load due to race conditions, forcing a rewrite of the critical path. At peak traffic, compensating transactions hit roughly a 20% failure rate. The frontend also paid for the architecture with in the range of 4-7 seconds of UI latency while it polled for confirmation.

That is the saga-pattern failure in high-contention inventory reservations problem in plain clothes. It is not an academic edge case. It is what happens when the business asks for a locked seat and the architecture offers a suggestion box.

What to use instead

  • Database transactions when the state lives together and the boundary is honest.
  • Request-response APIs when the caller needs the answer before continuing.
  • A modular monolith when the domain is still one product with several code modules.
  • A small orchestration service when steps cross systems but the workflow needs one owner.

Eventual consistency becomes hostile when users see contradictory states, support teams cannot explain what happened, and product managers start demanding compensating patches that make the system even harder to reason about. That is the UI-latency problem I want teams to remember before they draw another cheerful saga diagram.

Avoid it when nobody owns the full failure path

EDA requires mature operational ownership. Not enthusiasm. Not a platform slogan. Ownership.

Before a team publishes a business event, I want to know who owns the dead-letter queue, retry policy, idempotency keys, poison message handling, trace propagation, and replay procedure. I also want names, not team aliases that resolve to nobody during an incident.

Community observation suggests the nastiest incidents are rarely caused by the broker itself. They happen in the gaps between teams. During one cross-border data processing incident, three EU-based teams spent hours arguing because a dead-letter queue lacked context. The payload was malformed, the trace was incomplete, and the consumer had silently stopped processing the previous day.

Content creation workspace, clean aesthetic but unposed a laptop tilted slightly off-center on a wooden

The measured pattern matched the mood: about 40% of critical incidents involved cross-team boundary disputes over malformed payloads, and poison messages lacking proper trace propagation took roughly 22-28 hours on average to resolve.

The broker is not a responsibility disposal unit

Publishing an event is not the same as completing a business outcome.

If the producer can throw a message over the wall and declare success, the system has not become decoupled. It has become deniable. One catch: ownership matrices only work when platform teams can block deployments that lack dead-letter runbooks.

Quick Tip: For every topic, write down the owner, the replay command, the expected duplicate behavior, and the human escalation path before the first production message lands.

Do not publish events before the domain has stopped wobbling

Beginners often treat events as flexible because JSON feels flexible. That feeling lasts until the third downstream consumer depends on a field name you considered temporary.

Unstable domains make poor event contracts. In a greenfield compliance project, legal requirements shifted every few weeks. Each renamed concept became a compatibility problem for unknown consumers. Over the first year, roughly 75% of schema changes required coordinated, multi-team deployments to avoid breaking consumers. The team burned 18-24 developer days per month on backward compatibility layers for a domain that had not settled yet.

That is schema drift. Once several services consume an event, a field stops being an implementation detail. It becomes a public promise.

Progression path for a wobbling domain

  1. Keep the model inside one deployable boundary while language changes quickly.
  2. Expose explicit APIs for the few use cases that truly need integration.
  3. Record internal state changes if you need history, but do not publish them as domain events yet.
  4. Promote only stable business facts to integration events.

The practical rule is simple: name events in past tense after business facts, not technical mutations. ContractSigned has a fighting chance. ContractRowUpdated is database exhaust.

Fake domain events like UserUpdated and RecordChanged usually mean the team is streaming noise. Sometimes that is useful for replication. Fine. Call it replication. Do not pretend it is a business language.

Use boring designs until events earn their keep

The better default is not anti-EDA. It is anti-theater.

Start with a modular monolith when the team needs fast refactoring and shared transactions. For callers that need a response, explicit API calls are enough. Scheduled jobs fit periodic work where nobody cares about sub-second delivery. A workflow engine fits processes with state, timers, approvals, and human intervention. Use a transactional outbox when integration events are useful but dual-write bugs would be unacceptable.

In one KYC process, orchestration looked less elegant on paper than choreography. It also made the system legible. The central workflow engine let support staff see where an application sat, why it stopped, and who needed to act next. Moving the core flow away from pure choreography took 6-9 weeks, but the transactional outbox pattern cut dropped state transitions by about 90%.

Synchronous is not primitive

Synchronous APIs get sneered at in architecture meetings because they look old. That is lazy thinking. For user-driven operations, a direct call is often more observable, more testable, and easier to explain.

A request comes in. A service calls another service. The answer returns. Logs line up. Tests can assert the outcome. A support engineer can reproduce the path without reading six topic subscriptions and guessing which consumer version handled the message.

Summary: Boring designs are not a lack of ambition. They are a way to preserve optionality until the domain and failure modes deserve more machinery.

When I would actually choose event-driven architecture

Events do have good homes.

I would choose event-driven architecture for audit trails, integration events, analytics pipelines, cache invalidation, asynchronous notifications, and fan-out to independent consumers. In those cases, the consumer can be useful without blocking the producer. Delayed processing is acceptable. The producer does not need to know the final outcome.

We kept EDA for GDPR audit trails and analytics ingestion pipelines because those consumers were genuinely independent. The analytics pipeline reached just under 100% uptime once decoupled from the primary transactional database, and non-critical compliance reporting could tolerate 2-4 hours of processing delay. That is the kind of slack events need.

The decision rule

Use events when three things are true:

  • Consumers can produce value independently of the producer.
  • Delayed processing does not confuse users or violate the business promise.
  • The producer does not need to know whether every downstream activity finished.

Then pay the operational tax up front: observability, schema governance, replay strategy, idempotent consumers, and named ownership for every topic. If you need a common event envelope, the CloudEvents specification is a reasonable place to start. It will not fix your domain model, but at least it gives teams a shared shape for the message.

That is the whole point. Event-driven architecture is not architectural seasoning. It is a sharp tool. Use it where delayed, independent reaction is the point, not where the team is trying to avoid admitting that one workflow still needs one accountable path.

Never Miss an Update

Fresh insights every week.

No spam. Unsubscribe anytime.

Your Thoughts

Share your thoughts.

Join the Discussion

Customise cookies