DEV Community

Cover image for You're Not Ready for Event-Driven Architecture
turboline-ai
turboline-ai

Posted on

You're Not Ready for Event-Driven Architecture

Most teams adopt event-driven architecture and then spend the next six months slowly discovering why it's harder than they expected. Not because the tooling is bad. Not because the team isn't smart. But because they started with the wrong mental model.

They treated events like method calls with a queue in front.

That framing is the root of almost every EDA failure I've seen.

A Method Call Is a Promise. An Event Is Not.

When you call a function, you get a contract: the call either succeeds, fails, or throws. You know immediately. You can reason about it locally.

Events give you none of that. An event is a fact broadcast into the world, and what happens next is genuinely outside your control. The consumer might process it twice. It might arrive after a later event that depended on it. It might not arrive at all.

These are not edge cases you handle after launch. They are the baseline operating conditions of any distributed system moving data asynchronously. If you design your consumers assuming happy-path delivery, you are not building a resilient system. You are building a system that will corrupt state quietly and fail loudly at the worst possible moment.

The Complexity Cost Is Real. Pay It Deliberately.

Event-driven architecture earns its complexity when you genuinely need one of three things: loose coupling between services that change at different rates, natural buffering for workloads with unpredictable spikes, or a durable audit trail of everything that happened.

If you don't need at least one of those, you're adding distributed systems complexity without getting the benefits. "We're doing microservices, so we need a message bus" is not a reason. It's the setup for a debugging nightmare six months later when two services are publishing subtly incompatible events and nobody can trace what went wrong or when.

Events should be a deliberate architectural choice, not a default because the infrastructure team set up Kafka.

Three Things That Are Non-Negotiable

If you've decided the tradeoff is worth it, there are three properties your system needs before it's actually production-ready.

Idempotent consumers. Brokers guarantee at-least-once delivery. That means your consumer will see duplicates. Not sometimes. Routinely. Your processing logic needs to handle the same event arriving twice without corrupting state. A common pattern is tracking a unique event ID in a processed-events table before applying side effects:

def handle_order_placed(event):
    if db.already_processed(event["event_id"]):
        return

    db.mark_processed(event["event_id"])
    inventory.reserve(event["order_id"], event["items"])
Enter fullscreen mode Exit fullscreen mode

This isn't defensive programming. It's the minimum viable contract for any event consumer.

Dead-letter queues. When a consumer fails to process an event after retries, that event needs to go somewhere visible. Not silently dropped. Not stuck in an infinite retry loop. A dead-letter queue gives you a recoverable state: operators can inspect what failed, fix the root cause, and replay. Without it, you have no idea what your system has and hasn't processed. That ambiguity compounds fast.

Versioned schemas. Producers and consumers don't deploy together. They drift. A consumer built against a schema from three months ago will break the moment a producer adds a required field or renames a key. Schema registries with enforced versioning exist specifically to prevent this. Treat your event schema with the same rigor you'd treat a public API, because that's exactly what it is.

The Real Discipline Is Knowing When Not To Use It

The engineers who are best at event-driven architecture are also the ones most willing to say "we don't need this here." A synchronous HTTP call between two services that are always deployed together, always need a real-time response, and have no buffering requirement is not a failure of architectural imagination. It's the right tool.

The hard skill isn't wiring up a Kafka cluster. It's accurately assessing whether the coupling you're breaking is worth the observability, retry logic, schema management, and operational overhead you're taking on in its place.

Design for failure from day one, or don't use the architecture at all. There's no middle ground that works in production.

Top comments (0)