Event driven architecture is a design pattern in which services communicate by producing and consuming events, discrete records of something that happened, rather than calling each other directly through synchronous requests. An order service, for example, publishes an "order placed" event to a message broker, and any number of other services, inventory, shipping, billing, notifications, can subscribe to that event and react to it independently, without the order service needing to know they exist or calling each of them directly. The event itself is just a fact about something that occurred, and it's up to interested consumers to decide what to do with it.
The reason event driven architecture exists is that direct, synchronous service-to-service calls create tight coupling that becomes a real liability as a system grows. If the order service has to directly call the inventory service, the shipping service, and the billing service every time an order is placed, then adding a new consumer, say a fraud detection service, requires modifying the order service's code, and if any one of those direct calls fails or is slow, it can block or fail the entire order flow. Event driven architecture decouples the producer of information from its consumers, so the order service can fire off an event and move on, and new consumers can be added later without ever touching the order service again.
What distinguishes event driven architecture from other forms of service communication, particularly REST APIs and synchronous request-response calls, is this producer-consumer decoupling and the shift from imperative "do this now" calls to declarative "this happened" facts. In a synchronous system, the caller knows exactly who it's calling and waits for a response. In an event driven system, the publisher often doesn't know who, if anyone, is listening, and doesn't wait for a response at all. This requires infrastructure to carry events reliably between services, typically a message broker or event streaming platform like Kafka, RabbitMQ, or a cloud provider's managed equivalent, which handles the durability, ordering, and delivery guarantees that direct calls don't need to worry about.
By 2026, event driven architecture is a standard part of the toolkit for any organization running a nontrivial microservices system, and event streaming platforms, Kafka in particular, have become common infrastructure well beyond the internet-scale companies that popularized the pattern. It has also become closely tied to real-time data needs, from live analytics dashboards to fraud detection to personalization, all of which depend on reacting to events as they happen rather than waiting for a batch process to run later. At the same time, more teams have also become more honest about the operational complexity it introduces, which has led to more careful, deliberate adoption instead of applying it everywhere by default.
This page covers why event driven architecture exists, how the core patterns, publish-subscribe and event streaming, actually work, how it compares with synchronous request-response communication, where it fits and where the added complexity isn't worth it, and how a team adopts it without creating a system nobody can fully reason about. The durable idea is that decoupling how systems find out about changes from how they react to those changes is a powerful tool for managing complexity as a system grows, and understanding that trade lets a team decide deliberately where it's worth the operational cost.
The most fundamental pattern in event driven architecture is publish-subscribe, often shortened to pub-sub. A publisher emits an event to a named channel or topic without knowing or caring who, if anyone, is subscribed to it. Subscribers register interest in a topic and receive every event published to it. This is what allows the order service in a typical example to publish "order placed" once, while inventory, shipping, billing, and any future service all receive that same event independently, each proceeding with whatever logic is relevant to them, in parallel, without waiting on each other.
Event streaming, exemplified by platforms like Apache Kafka, extends this idea by treating events not just as one-off notifications but as a durable, ordered log that can be replayed. Rather than a message that's delivered once and then gone, an event in a stream sits in a persistent log, and consumers can read from any point in that log, including reprocessing events from the past if, for instance, a bug in a consumer's logic needs to be fixed and the consumer needs to reprocess a week of history to correct the resulting bad data. This replayability is a meaningfully different capability from simpler message queue systems, and it's a big part of why Kafka in particular became foundational infrastructure for real-time data pipelines, not just service-to-service messaging.
Ordering and delivery guarantees are where a lot of the real engineering complexity in event driven systems lives. Different platforms offer different guarantees, at-least-once delivery, where a consumer might receive the same event more than once, exactly-once delivery, which is harder to achieve and often comes with performance tradeoffs, and varying guarantees around whether events within a topic are processed in the order they were published. Systems built on event driven architecture need to be deliberate about which of these guarantees they actually need, since assuming a stronger guarantee than the infrastructure provides is a common and often subtle source of bugs, particularly around duplicate processing.
Event schemas, the structure and shape of the data inside an event, also need active management over time, in much the same way API contracts do. If the order service changes what fields it includes in the "order placed" event, every downstream consumer relying on the old shape can break, and because those consumers might belong to entirely different teams who don't know a change is coming, schema changes in event driven systems benefit from the same kind of deliberate versioning and communication that any shared contract needs, often enforced through a schema registry that validates events against an agreed structure before they're published.
This need for schema discipline is arguably sharper in event driven systems than in synchronous APIs, precisely because a producer often genuinely doesn't know who's consuming its events. With a REST API, a team can at least look at access logs to get some sense of who's calling a given endpoint before changing it. With a broadly subscribed event topic, a new consumer can start reading from it without ever notifying the producing team at all, which means the producing team has to assume, by default, that any change to an event's shape could break something they have no visibility into, and design their change process accordingly.
The clearest way to understand event driven architecture is by contrast with the synchronous request-response model most engineers learn first, where a client calls a server, the server does the work, and the client waits for and receives a direct response before continuing. This model is simple to reason about: you can read the code and see exactly what happens next, and if something fails, the failure is visible immediately to the caller. Its limitation shows up when a single action needs to trigger work in many other systems, since each direct call adds latency, and a failure in any one call can block or fail the whole operation, even if that particular downstream effect wasn't critical to the immediate user-facing outcome.
Event driven architecture trades that immediate visibility for resilience and flexibility. If the notification service is down when an order is placed, in a synchronous model that might fail the entire order, in an event driven model the order still succeeds, the event still gets published, and the notification service will process it whenever it comes back up, or a retry mechanism will handle the gap. This resilience is genuinely valuable for the exact kinds of operations that don't need to happen at the same instant as the triggering action, sending a confirmation email doesn't need to block the checkout flow the way charging a payment method does.
The tradeoff is that reasoning about what actually happens after an event is published becomes much harder. In a synchronous chain of calls, you can trace the exact path of execution by reading the code. In an event driven system, a single event might trigger five different consumers, each running independently, each potentially failing or retrying on its own schedule, and there's no single place in the code that shows you the whole picture. This is why distributed tracing and good observability tooling aren't optional extras for event driven systems, they're close to a prerequisite for being able to debug problems at all once the system has more than a couple of event types and consumers.
Most real systems, sensibly, use both patterns together rather than picking one exclusively. Synchronous calls remain the natural fit for anything where the caller genuinely needs an immediate answer to proceed, checking whether a payment succeeded, validating that a user is authorized to take an action. Event driven communication is the better fit for anything that can happen slightly later and doesn't need to block the primary flow, updating a search index, sending a notification, feeding an analytics pipeline. Recognizing which category a given interaction falls into is a more useful design skill than treating event driven architecture as something to apply uniformly everywhere.
A useful test many teams apply is asking what should happen if a particular downstream effect simply failed silently for an hour. If the honest answer is "nothing catastrophic, we'd just want to know about it and retry," that's a strong signal the interaction belongs on the event driven side. If the honest answer is "the user would see something broken right now, or money could move incorrectly," that's a strong signal it belongs on the synchronous side, regardless of how appealing the decoupling benefits of events might seem in the abstract.
Event driven architecture fits particularly well in systems with many independent consumers reacting to the same underlying facts, which is common in mature microservices architectures where a single business event, a user signing up, an order being placed, an inventory level changing, genuinely needs to trigger work across many different, loosely related services. It's also a strong fit for real-time processing needs: fraud detection systems that need to react to a transaction the moment it happens, personalization engines that update recommendations as user behavior streams in, and live dashboards that reflect the current state of a system rather than yesterday's batch export.
It fits especially well when the set of consumers is expected to grow over time and isn't fully known upfront. A payments platform launching today might only need order events consumed by inventory and shipping, but a well-designed event driven setup means that when a fraud detection team or a new analytics team shows up eight months later, they can simply subscribe to the existing event stream without anyone needing to modify the original order service at all. This forward compatibility is one of the more durable, underappreciated benefits of the pattern.
It fits less well for simple systems with a small, fixed, well-known set of interactions, where the operational overhead of running and monitoring a message broker, managing schemas, and building observability into asynchronous flows isn't justified by the actual coordination problem being solved. A small application with two or three services calling each other in well-understood ways often does just fine with straightforward synchronous APIs, and introducing event driven architecture there tends to add real infrastructure and cognitive overhead without a corresponding benefit.
It also fits poorly for operations that genuinely require an immediate, guaranteed, synchronous answer before the caller can proceed, authorizing a payment being the clearest example. Trying to force this kind of interaction into an asynchronous, eventually-consistent event driven model usually produces awkward workarounds, and it's a sign the pattern is being applied where it doesn't belong rather than a sign the team hasn't implemented it well enough. Recognizing this boundary early saves a lot of wasted effort trying to force-fit the wrong pattern.
Any honest treatment of event driven architecture has to deal with the fact that consumers fail, and they fail independently of the producer and of each other. A notification service consuming "order placed" events might be temporarily down, might throw an error on a particular malformed event, or might simply fall behind under heavy load. Because the producer isn't waiting for a response the way it would in a synchronous call, it has no way of knowing any of this happened unless the system is specifically built to surface it. Handling this well is one of the less glamorous but more important parts of running event driven systems in production.
Retry logic is the first line of defense, and most message broker and streaming platforms support some form of automatic redelivery when a consumer fails to acknowledge processing an event successfully. This works well for transient failures, a brief network blip, a downstream dependency that was momentarily unavailable, but it doesn't help with events that fail every time they're processed, typically because the event itself is malformed or triggers a bug in the consumer's logic. Retrying those indefinitely just wastes resources and can clog up processing for events behind them in the same stream.
This is where dead letter queues come in. After a configured number of failed attempts, a message gets moved out of the main processing path and into a separate queue specifically for events that couldn't be handled successfully, where they wait for a human or a separate automated process to investigate rather than blocking or endlessly retrying inline. A well-run event driven system treats a growing dead letter queue as an alerting signal worth paying attention to, since a spike there often means a consumer has a bug that's silently failing to process a whole category of events, a problem that, without this mechanism, might otherwise go unnoticed until someone asks why a downstream effect never happened.
Idempotency is the last piece of this picture, and it matters because many delivery guarantees are "at-least-once" rather than "exactly-once," meaning a consumer should expect to occasionally receive the same event more than once and needs to handle that gracefully. A payment consumer that isn't idempotent might charge a customer twice if it happens to receive the same "process payment" event twice due to a retry, which is a business-critical bug, not just a theoretical inconvenience. Designing consumers so that processing the same event multiple times produces the same end result as processing it once, often by tracking which event identifiers have already been handled, is a foundational discipline in event driven systems, not an optional refinement.
Teams adopting event driven architecture for the first time do best when they start with a single, well-understood use case rather than trying to convert an entire system's communication patterns at once. Picking one genuinely asynchronous interaction, something like sending notifications after an order is placed, and building it end to end, including the message broker setup, schema definition, and monitoring, gives the team a real, working example to learn from and build confidence around before expanding the pattern further.
Investing in observability early is not optional in any serious sense, even though it's tempting to defer it. Distributed tracing that can follow an event from its publisher through every consumer that reacts to it is what makes event driven systems debuggable at all, and teams that skip this step early often find themselves, months later, unable to answer basic questions like why a particular order's notification never went out, because there's no way to trace the event's path through the system after the fact.
Schema management deserves the same deliberateness API contracts get in an API first workflow. Deciding early on a schema registry or an equivalent convention, and agreeing on rules for what counts as a breaking change to an event's structure, prevents the quiet, hard-to-diagnose breakage that happens when one team changes an event's shape without realizing three other teams' consumers depend on the old one. This is especially important in event driven systems because, unlike a direct API call, a producer often has no visibility into who's consuming its events at all.
Finally, teams should be honest about the operational cost of running message broker infrastructure, whether self-hosted Kafka or a managed cloud alternative, and should not assume it comes for free just because managed services exist. Someone needs to own capacity planning, monitoring for consumer lag, and incident response when the broker itself has problems. Teams that treat this infrastructure as a serious, owned piece of the system, rather than a black box that just works, are the ones that get event driven architecture's real benefits without being blindsided by its operational demands later.
Event driven architecture is a design pattern where services communicate by publishing and reacting to events, records of something that already happened, rather than calling each other directly, allowing producers and consumers to be decoupled and new consumers to be added without changing the producer.
A message queue is one piece of infrastructure that can support event driven architecture, typically handling point-to-point delivery of individual messages. Event streaming platforms like Kafka extend this with a durable, replayable log of events that many consumers can read independently, which is a broader capability than a simple queue provides.
REST APIs are typically synchronous, a caller sends a request and waits for a direct response. Event driven architecture is usually asynchronous, a publisher emits an event and moves on without waiting, and any number of independent consumers react to it on their own schedule.
Mainly to reduce tight coupling. Direct calls require the caller to know about and depend on every service it calls, and a failure in any one of them can block the whole operation. Event driven architecture lets services react independently, and new consumers can be added later without modifying the original producer.
The main challenges are debuggability and complexity. Tracing what happens after an event is published requires distributed tracing and strong observability, since there's no single code path to read the way there is with direct synchronous calls. Managing event schema changes over time is another significant challenge.
No, Kafka is one popular platform used to implement event driven architecture, specifically an event streaming platform that supports a durable, replayable log of events. Event driven architecture is the broader design pattern, and it can also be implemented with other message brokers like RabbitMQ or cloud-native equivalents.
When the system is simple, with a small, well-known set of interactions, or when an operation genuinely requires an immediate, guaranteed answer before the caller can proceed, such as authorizing a payment. In these cases, the operational overhead of event driven architecture usually isn't justified.
Many teams use a schema registry that validates events against an agreed structure before they're published, along with clear rules for what counts as a breaking change. This mirrors how API contracts are versioned, since a producer often can't see every consumer depending on the current event shape.
It depends on the platform and configuration, not on the pattern itself. Some platforms and setups guarantee ordering within a given partition or topic, others prioritize throughput over strict ordering, so teams need to be deliberate about which guarantee they actually require rather than assuming the strongest one by default.