Event-Driven Architecture Is More Than a Message Queue
Marcus Ionescu · 2025-11-14 · 3 min read
We're regularly brought in to "make the platform event-driven," and the first thing we check is whether the team means it architecturally or just wants a message broker. Adding Kafka or SQS between two services that still expect synchronous, ordered, exactly-once behavior from each other doesn't produce an event-driven system — it produces a request-response system with worse debuggability and a new class of failure modes. The transport is the easy 10%. The architecture is the other 90%.
Decide who owns an event before you name it
The single highest-leverage decision in an event-driven system is event ownership: which service is the authoritative source of a given event type, and which services are consumers. Get this ambiguous and you get the failure pattern we see constantly — two services both publishing an OrderUpdated event with slightly different schemas and slightly different meanings, and downstream consumers guessing which one to trust. We require an event catalog with a single named owner per event type before implementation starts, the same discipline you'd apply to API ownership, because an event schema is a public contract exactly like an API endpoint is, even though it feels more casual to add a field to a JSON payload than to an OpenAPI spec.
Ordering guarantees are a per-stream decision, not a platform default
"Does this system guarantee ordering" is the wrong question — the right question is "does this system guarantee ordering within a partition key, and have we chosen the right partition key for every consumer's correctness requirement." A payments system partitioned by account_id gives you strict per-account ordering, which is usually what you need, but gives you no cross-account ordering guarantee at all, which surprises teams building a global audit view on top of it. We map every consumer's ordering assumption against the actual partition scheme explicitly, because "events usually arrive in order" is not a guarantee, it's a description of what happened in testing.
Replay is a feature you have to design for, not something you get for free
The single biggest advantage of event-driven architecture over point-to-point calls is that you can replay history to rebuild state, backfill a new consumer, or recover from a bug. Almost nobody actually gets this for free — it requires event schemas that are self-contained enough to replay without external lookups that may no longer resolve to the same values (a price at the time of order vs. a price looked up at replay time is a classic bug), retention policies long enough to support replay windows the business actually needs, and idempotent consumers that can safely process the same event twice. We treat "can we replay the last 90 days into a fresh consumer and get the same end state" as an acceptance test for the architecture, not an aspiration.
Exactly-once is a lie you can approximate with idempotency
We don't design for exactly-once delivery, because most transports genuinely don't provide it end-to-end once you account for producer retries and consumer crashes mid-processing. We design for at-least-once delivery plus idempotent consumers — a deduplication key derived from the event, checked against a processed-events store before any side effect fires. This is more honest about what the infrastructure actually guarantees, and it's the pattern that survives an incident where a consumer crashes after processing an event but before acknowledging it, which will happen eventually regardless of how reliable your broker's SLA claims to be.
The dashboard question you should be able to answer
When we hand off an event-driven system, the team should be able to answer, for any event type: who publishes it, who consumes it, what happens if a consumer falls behind, and how far back they can replay. If any of those four answers is "we're not sure," the system isn't event-driven yet — it's asynchronous, which is a real architecture choice but a different and less resilient one than the team probably thinks they bought.