What Is Event-Driven Architecture (EDA)?

Event-driven architecture (EDA) is a software design style in which services communicate by producing and reacting to events — records of things that have already happened — rather than by calling each other directly. A service announces a change to its own state, and any other service that cares can respond, without the two ever knowing about each other.

That definition is easy to state and much harder to apply well. This page covers what an event actually is, how EDA differs from the command-driven systems it is often confused with, the patterns that make up the style, the failure modes it introduces, and how to build event-driven systems on Valkey and Redis from Java.

What is event-driven architecture?

In a conventional request-response system, control flows through explicit calls. The order service calls the inventory service, waits, then calls the payment service, waits, then calls the notification service. The order service holds the entire workflow in its own code, which means it must know every downstream participant and be changed whenever that list changes.

In an event-driven system, the order service publishes a single fact — OrderPlaced — and stops. It does not know that inventory, payments, and notifications exist. Each of those services subscribes to the events it cares about and decides for itself what to do. Adding a fourth consumer, such as a fraud checker or an analytics pipeline, requires no change to the order service at all.

The shift is in who holds the knowledge. Command-driven systems concentrate workflow knowledge in the caller. Event-driven systems distribute it among the consumers. That is the entire trade, and every benefit and every difficulty of EDA follows from it.

EDA is an architectural style, not a technology. It can be implemented with a log, a broker, a managed cloud service, or an in-memory data store. The infrastructure determines durability and throughput, not whether a system is event-driven.

Events vs. commands

The most common way to get EDA wrong is to send commands and call them events. The distinction is worth being precise about.

A command is an instruction to do something. It is addressed to a specific handler, it expects to be acted on, and it is named in the imperative: ReserveInventory, ChargeCard, SendEmail. The sender owns the outcome and generally wants to know whether it succeeded.

An event is a statement that something has already happened. It is addressed to nobody in particular, it carries no expectation, and it is named in the past tense: OrderPlaced, PaymentCaptured, InventoryReserved. The publisher has no opinion about what happens next and does not wait to find out.

A practical test: if you cannot add a new consumer without the producer knowing about it, you are sending a command with an event's name. A message called OrderPlaced that exists solely so the billing service will charge the customer is a ChargeCustomer command in disguise, and it will couple the two services just as tightly.

Both are legitimate and most systems use both. What makes an architecture event-driven is that events are the primary means of integration, with commands reserved for cases where the sender genuinely owns the outcome.

How event-driven architecture works

Four components make up a typical event-driven system:

  • Event producer: A service that detects a state change and publishes an event describing it.
  • Event: An immutable record of what happened, usually carrying a type, an identifier, a timestamp, and a payload.
  • Event broker or channel: The infrastructure that receives events and routes them to interested parties — a topic, a log, or a message broker.
  • Event consumer: A service that subscribes to a category of events and takes action when one arrives.

The flow is short:

  1. Something happens in a producer. A customer places an order.
  2. The producer writes an event describing that fact and publishes it to a channel. It does not wait for a response and does not care whether anyone is listening.
  3. The broker accepts the event and makes it available to every subscriber of that channel — and, if it is durable, retains it for subscribers that are not currently connected.
  4. Each consumer receives the event independently and processes it on its own schedule, at its own pace, with its own failure handling.

Because consumers are independent, one failing does not affect the others, and a slow consumer does not slow the producer. Event-driven services are decoupled not only in code but in time.

Event-driven architecture patterns

"Event-driven" covers several distinct patterns with different trade-offs. They are frequently conflated, and choosing between them deliberately is most of the design work.

Event notification. The event carries the bare minimum — an event type and an identifier. A consumer that needs more calls back to the producer's API for the details. This keeps events small and coupling low, but it reintroduces synchronous calls and can generate a burst of callbacks every time a popular event fires.

Event-carrying state transfer. The event carries enough state that consumers never need to call back. Consumers can maintain their own local copy of the data they need and keep working even when the producer is unavailable. The costs are larger payloads, duplicated data, and consumers that may act on a slightly stale view.

Event sourcing. Rather than storing current state and overwriting it, the system stores the full sequence of events and derives state by replaying them. The log becomes the system of record. This gives you a complete audit trail, the ability to reconstruct state at any past moment, and the option to build new projections from history. It also introduces real difficulty: events are permanent, so schema evolution must be handled forever, and replaying a long history requires snapshotting to stay practical. Event sourcing is a legitimate pattern within EDA, not a synonym for it — most event-driven systems do not use it.

CQRS. Command Query Responsibility Segregation separates the write model from the read models, with events propagating changes between them. It pairs naturally with event sourcing but does not require it, and is worth adopting only when read and write workloads differ enough to justify two models.

Choreography vs. orchestration. When a business process spans several services, either each service reacts to the previous one's events and the workflow emerges from their interaction (choreography), or a single coordinator drives the sequence explicitly (orchestration). Choreography is more decoupled but makes the end-to-end process harder to see, since it exists in no single place. Orchestration is easier to reason about and monitor but concentrates knowledge back into one component. Transactions spanning several services are handled in either style with the saga pattern rather than a two-phase commit.

Event-driven architecture vs. request-response

Request-responseEvent-driven
CouplingCaller must know the calleePublisher knows no consumers
TimingSynchronous; caller waitsAsynchronous; producer continues
FailureCallee down means caller failsConsumer down means messages wait
ConsistencyImmediateEventual
Adding a consumerRequires changing the callerRequires no producer change
DebuggingA single stack traceDistributed tracing required
Best forQueries that need an answer nowReacting to state changes

Neither is universally better. Most systems use both: synchronous calls for queries the caller cannot proceed without, events for propagating changes.

Event-driven architecture vs. microservices

These are frequently treated as the same decision. They are orthogonal.

Microservices describe how an application is decomposed: into small, independently deployable services that own their own data. Event-driven architecture describes how components communicate: by publishing and reacting to events rather than calling one another.

You can build microservices that talk exclusively over synchronous REST, and many organisations do. You can also build an event-driven monolith, where modules inside one deployable communicate through an in-process event bus. Neither is a contradiction.

They are combined so often — the result is usually called event-driven microservices — because they solve adjacent problems. Decomposing a monolith creates chains of synchronous calls, and those chains are fragile: each hop adds latency, and any unavailable service fails the whole request. Event-driven communication removes that temporal coupling. The reverse holds too: EDA is most valuable when many independent consumers exist, which is what microservices tend to produce.

Benefits of event-driven architecture

  • Loose coupling. Producers and consumers are developed, deployed, and scaled independently, because neither holds a reference to the other.
  • Extensibility. New consumers can be added to an existing event stream without redeploying the producer — usually the most valuable property in practice.
  • Resilience. A failing consumer does not fail the producer; with a durable channel its messages wait until it recovers.
  • Independent scaling. A consumer under load scales without touching anything upstream.
  • Responsiveness. Producers do not block on downstream work, so user-facing latency reflects only what the user waits for.

Challenges and when not to use event-driven architecture

EDA is not a free improvement. It exchanges one set of problems for another, and the new set is harder to debug.

  • Eventual consistency. There is a window in which services hold different views of the same entity. A user who places an order and immediately refreshes may not see it. This is not a bug but a property to design around, and it reaches into the user interface.
  • Ordering. Events do not necessarily arrive in the order they were produced, particularly across partitions or after a retry. If your consumer requires order, you must arrange for it explicitly — typically by partitioning on a key so that all events for one entity travel the same path.
  • Duplicate delivery. Durable messaging is normally at-least-once, which means consumers will occasionally see the same event twice. Idempotency is a requirement, not a refinement: processing PaymentCaptured twice must not charge the customer twice.
  • Observability. No stack trace spans an event-driven workflow. Understanding why something did not happen means correlating logs across services, which requires correlation IDs and distributed tracing from the beginning rather than after the first incident.
  • Schema evolution. Once an event is published and consumed by services you do not control, its shape is a public contract. Changing it requires versioning and a migration path, indefinitely.
  • Operational surface. The event channel becomes a tier-0 dependency. If it is down, services stop communicating. That is a new system to run, monitor, secure, and capacity-plan.

When not to use it. EDA earns its complexity when consumers outnumber producers and change independently of them. It is usually the wrong choice when a single team owns every service, since the coupling it removes is coupling you can simply refactor away; when a workflow requires immediate consistency, such as checking a balance at the moment of a decision; when there is exactly one consumer and no prospect of a second, where a direct call is simpler and easier to debug; and when the team cannot yet invest in tracing and monitoring, because an event-driven system without observability is very difficult to operate.

Event-driven architecture examples and use cases

  • E-commerce order processing. One OrderPlaced event drives inventory reservation, payment capture, fulfilment, confirmation email, and analytics — each independent, each able to fail and retry alone.
  • Payments and fraud detection. Transaction events feed a scoring service in parallel with the main payment flow, so risk analysis does not add latency to the customer's request.
  • Real-time analytics and telemetry. Application and device events stream into a data pipeline for aggregation and dashboards.
  • Notifications and fan-out. One event reaches many recipients or channels at once — see the messaging fanout pattern.
  • Cache invalidation. A change in the system of record publishes an event that every application node uses to evict its stale entry.
  • Audit and compliance. An append-only event log provides a tamper-evident record of everything that happened, which is often a regulatory requirement rather than a design preference.

Event-driven architecture tools and technologies

Implementations fall into a few families:

  • Log-based systems such as Apache Kafka, Apache Pulsar, and Redis and Valkey Streams retain events in an ordered, replayable log. Consumers track their own position, so history can be re-read.
  • Broker-based systems such as RabbitMQ and ActiveMQ route messages through queues and exchanges, with acknowledgments and per-message delivery semantics. In Java these are commonly accessed through JMS.
  • Managed cloud services such as Amazon EventBridge, SNS/SQS, Google Pub/Sub, and Azure Event Grid remove the operational burden in exchange for cost and provider coupling.
  • In-memory data stores such as Valkey and Redis provide both ephemeral publish/subscribe and durable streams, and are often already present in the stack for caching or locking.

For direct comparisons, see Redis vs. Kafka, Redis Streams vs. Kafka, and Apache Kafka alternatives.

Event-driven architecture with Valkey and Redis

Most vendor material on EDA assumes you will add a dedicated broker to your stack. That is often the right answer at high throughput, but it is not the only one, and a broker cluster is a significant operational commitment.

Valkey and Redis offer two distinct mechanisms. Native pub/sub delivers only to subscribers that happen to be connected, retaining nothing — well suited to cache invalidation, live presence, and dashboards, where a lost message is quickly superseded, and unsuited to order events. Streams are a durable, append-only log with consumer groups, acknowledgments, and reclaim of stalled messages — much closer to a log-based broker, and appropriate as an event backbone at moderate volume.

Neither provides the reliability machinery a production event system needs: delivery attempt limits, dead-letter destinations, delayed delivery, and message priority. Those come from application code or a client library.

The case for this approach is straightforward. If you already run Valkey or Redis for caching, distributed locking, or sessions, using it as the event channel avoids introducing a separate tier to deploy, monitor, secure, and staff. If your event volume genuinely warrants Kafka, use Kafka. Many systems described as event-driven do not.

Event-driven architecture in Java with Redisson

Java applications reach Valkey and Redis through a client library. Redisson exposes messaging as ordinary Java interfaces, so publishing an event is a method call:

// Order service: announce a fact and move on
RTopic topic = redisson.getTopic("order-events");
topic.publish(new OrderPlaced("A-1001", "sku-42", 2));

Consuming it in a different service, with no reference to the publisher:

// Inventory service: react to the fact
RTopic topic = redisson.getTopic("order-events");
topic.addListener(OrderPlaced.class, (channel, event) -> {
    inventory.reserve(event.getSku(), event.getQuantity());
});

Because durable delivery is at-least-once, consumers need to be idempotent. A shared, expiring set makes deduplication explicit — add returns false if the event has already been seen by any instance:

RSetCache<String> processed = redisson.getSetCache("processed-events");
if (processed.add(event.getId(), 24, TimeUnit.HOURS)) {
    inventory.reserve(event.getSku(), event.getQuantity());
}

This is a starting point rather than a complete answer: marking the event as seen before the work finishes means a crash in between drops it silently. Systems that cannot tolerate that either make the downstream operation naturally idempotent or pair the check with a transactional outbox.

Reliable messaging with Redisson PRO

Redisson PRO adds the guarantees that raw pub/sub and streams leave to the application. Reliable PubSub provides durable publish/subscribe through a topic → subscription → consumer model, with configurable retention, replay by position, ID, or timestamp, automatic redelivery of unacknowledged messages, and dead-letter topics. Reliable Queue covers the command side, with acknowledgments, delivery limits, delayed delivery, and dead-letter destinations.

For teams with existing Java messaging code, Redisson PRO implements the JMS API (2.0, 3.0, and 3.1) and provides a Spring Cloud Stream binder, so replacing RabbitMQ can be a configuration change rather than a rewrite. The messaging documentation covers configuration and delivery semantics in detail.

Event-Driven Architecture: Frequently Asked Questions

What is the difference between event-driven architecture and microservices?

Decomposition versus communication. Microservices concern how an application is split up; EDA concerns how the pieces talk. The two are independent — REST-based microservices and internally event-driven monoliths are both common — and they are combined so often because events remove the temporal coupling that makes long synchronous call chains fragile.

What is the difference between an event and a command?

A command instructs a specific handler to do something and is named in the imperative (ReserveInventory). An event states that something has already happened, is addressed to no one, and is named in the past tense (InventoryReserved). The practical test is whether you can add a new consumer without the sender knowing. If you cannot, it is a command regardless of its name.

Do you need Kafka for event-driven architecture?

No. Kafka is one implementation of a durable event log, and a strong one at high throughput, but EDA is a style rather than a technology. RabbitMQ, Pulsar, cloud services such as EventBridge, and Valkey or Redis Streams can all serve as the event channel. The choice depends on durability requirements, event volume, and operational capacity.

What is the difference between event sourcing and event-driven architecture?

Event-driven architecture is the broad style of communicating through events. Event sourcing is a persistence pattern in which the event log is the system of record and state is derived by replaying it. Event sourcing is always event-driven, but most event-driven systems are not — they publish events while storing current state conventionally.

What are the disadvantages of event-driven architecture?

Eventual consistency between services, no guaranteed message ordering without explicit partitioning, duplicate deliveries that require every consumer to be idempotent, debugging that demands distributed tracing rather than stack traces, event schemas that become permanent public contracts, and a message channel that becomes a critical operational dependency. These are the reason EDA is a poor fit for small systems with a single team and immediate consistency requirements.

Can Redis or Valkey be used for event-driven architecture?

Yes, with a caveat about which mechanism you use. Native pub/sub is fire-and-forget and appropriate only where losing a message is acceptable, such as cache invalidation. Streams provide a durable, replayable log with consumer groups and acknowledgments and can serve as a genuine event backbone. Features such as delivery limits and dead-letter destinations are not built in and come from a client library such as Redisson PRO.

Is pub/sub the same as event-driven architecture?

Pub/sub is a messaging pattern — a mechanism for delivering one message to many recipients. Event-driven architecture is an architectural style built on the idea that services communicate through events. Pub/sub is one common way to implement EDA, but you can use pub/sub in a system that is not event-driven, and you can build event-driven systems on logs or queues instead.

Similar terms