CLOUD.RICH / ANALYSIS

Event-Driven Architecture vs Request-Response: When Should You Introduce Asynchronous Systems?

· 16 min read

Event-driven architecture is often presented as the natural next step for systems that need to scale. Replace direct service calls with queues, publish events, add consumers, and suddenly the architecture appears more resilient and flexible.

In practice, asynchronous systems introduce a different class of complexity.

Messages can arrive twice. Consumers can fail halfway through processing. Events can be delivered out of order. A workflow that previously existed inside a single request may now be distributed across several services, queues, retries, and databases.

None of this makes event-driven architecture a bad choice. It simply means that asynchronous communication should solve a specific problem.

This article explains when request-response communication is enough, when asynchronous messaging becomes valuable, and what operational costs appear once events become part of the architecture.

Contents
  1. Request-Response Should Usually Be the Default
  2. What Changes When You Go Asynchronous?
  3. Commands, Events, and Messages Are Not the Same Thing
  4. Command
  5. Event
  6. Message
  7. The Real Benefits of Event-Driven Architecture
  8. 1. Decoupling Service Availability
  9. 2. Absorbing Traffic Spikes
  10. 3. Moving Slow Work Outside the User Request
  11. 4. Independent Consumer Scaling
  12. The Price: Eventual Consistency
  13. Retries Change Application Semantics
  14. Idempotency Is Not Optional
  15. Exactly-Once Delivery Is Usually the Wrong Mental Model
  16. Ordering Is More Expensive Than It Looks
  17. Dead-Letter Queues Are Part of the Design
  18. The Dual-Write Problem
  19. The Transactional Outbox Pattern
  20. Event Choreography vs Workflow Orchestration
  21. Choreography
  22. Orchestration
  23. Do Not Turn Every Service Call Into an Event
  24. Queue vs Pub/Sub
  25. Queue
  26. Publish-Subscribe
  27. When Do You Need an Event Stream?
  28. Observability Becomes More Important
  29. Consumer Lag Is Often More Important Than Queue Size
  30. Schema Evolution Becomes an Architecture Problem
  31. Event Replay Is Powerful — and Dangerous
  32. When Event-Driven Architecture Makes Things Worse
  33. Too Many Small Services
  34. Invisible Dependencies
  35. Harder Debugging
  36. Operational Overhead
  37. Eventual Consistency Everywhere
  38. Complex Recovery
  39. Distributed Transactions Become Business Workflows
  40. A Practical Decision Framework
  41. Five Signals That You May Actually Need Async Messaging
  42. 1. Non-critical downstream failures are breaking user requests
  43. 2. Traffic arrives faster than downstream systems can process it
  44. 3. Users are waiting for operations that do not need to finish immediately
  45. 4. Many independent systems react to the same business change
  46. 5. Different parts of the workflow need independent scaling
  47. Five Signs You Are Introducing Events Too Early
  48. 1. The system has very little traffic
  49. 2. Every event has exactly one producer and one consumer
  50. 3. Developers cannot explain the failure model
  51. 4. The business requires immediate consistency
  52. 5. The architecture exists mainly because a messaging technology is fashionable
  53. A Better Migration Path
  54. Step 1: Keep Core Transactions Synchronous
  55. Step 2: Move Non-Critical Side Effects to Background Processing
  56. Step 3: Add Idempotency and Retry Handling
  57. Step 4: Add Dead-Letter Handling and Operational Metrics
  58. Step 5: Introduce Domain Events Where Multiple Consumers Need Them
  59. Step 6: Add More Sophisticated Streaming Infrastructure Only When Required
  60. A Simple Architecture Is Often a Hybrid Architecture
  61. Final Takeaway

Request-Response Should Usually Be the Default

Most applications begin with synchronous communication.

A client sends a request. The server performs some work. The server returns a response.

Client
  |
  | POST /orders
  v
Order Service
  |
  | response
  v
Client

The model is simple because the entire operation has a clear beginning and end.

If the request succeeds, the caller receives a success response. If something fails, the caller receives an error.

This simplicity has significant operational value.

  • Failures are easier to understand.
  • Logs usually follow a single request path.
  • Debugging is relatively straightforward.
  • Users receive immediate feedback.
  • Consistency is easier to reason about.
  • Developers need fewer infrastructure components.

For many systems, this model can scale much further than teams initially expect.

A synchronous architecture does not automatically mean a tightly coupled monolith. Independent services can still expose APIs and communicate over HTTP or RPC.

The important question is not whether events are more modern.

The question is whether synchronous dependencies are creating a measurable limitation.

What Changes When You Go Asynchronous?

In an asynchronous architecture, the producer does not necessarily wait for the consumer to complete its work.

Order Service
      |
      | OrderCreated
      v
 Message Broker
    /   |   \
   v    v    v
Email  Stock  Analytics

The order service can publish an event and continue.

Other components process that event independently.

This changes the relationship between services.

The producer no longer needs every consumer to be available at the exact moment the event is created. Consumers can process messages later, retry failures, or scale independently based on queue depth.

This property is often called temporal decoupling.

It is one of the strongest reasons to introduce asynchronous messaging.

Commands, Events, and Messages Are Not the Same Thing

These terms are often used interchangeably, but they describe different intentions.

Command

A command asks another component to perform an action.

ChargePayment
GenerateInvoice
SendEmail

Commands are usually directed toward a specific handler.

Event

An event describes something that has already happened.

OrderCreated
PaymentCompleted
UserRegistered

The publisher does not necessarily know which systems will consume the event.

Message

Message is the broader transport concept. A message may contain a command, event, notification, or another unit of data.

Keeping these concepts separate makes distributed workflows easier to understand.

The Real Benefits of Event-Driven Architecture

Event-driven systems become useful when asynchronous communication solves a specific architectural constraint.

The most common constraints are availability dependencies, traffic spikes, long-running work, independent scaling, and integration between multiple downstream systems.

1. Decoupling Service Availability

Consider an order workflow.

Order Service
   |
   +--> Payment Service
   |
   +--> Inventory Service
   |
   +--> Email Service
   |
   +--> Analytics Service

If all calls happen synchronously, the order request may depend on every downstream service being healthy.

This creates an availability chain.

The email service should probably not determine whether an order can be accepted. Neither should the analytics platform.

Event-driven communication allows non-critical work to happen independently.

Order Service
      |
      | OrderCreated
      v
 Message Broker
      |
      +--> Email Consumer
      +--> Analytics Consumer
      +--> Fulfillment Consumer

If the analytics consumer is temporarily unavailable, order creation can continue.

The events remain available for later processing.

This is a meaningful reduction in coupling.

2. Absorbing Traffic Spikes

Synchronous systems often require downstream services to handle incoming traffic at approximately the same rate as the upstream system.

That becomes difficult when traffic arrives in bursts.

Imagine a service normally processing 500 jobs per minute but occasionally receiving 20,000 jobs during a short traffic spike.

Without buffering, the downstream service must either scale immediately or begin rejecting requests.

A queue changes the model.

Incoming Requests
      |
      v
     Queue
      |
      |
      v
 Worker Pool

Producers can continue accepting work while consumers process the backlog at a sustainable rate.

This is often called load leveling.

Queue depth then becomes an important scaling signal.

Instead of scaling workers only from CPU utilization, the platform can scale based on the amount of pending work.

3. Moving Slow Work Outside the User Request

Some operations simply do not belong in the synchronous request path.

Examples include:

  • video encoding,
  • PDF generation,
  • image processing,
  • large data imports,
  • report generation,
  • email delivery,
  • machine learning inference pipelines,
  • bulk synchronization with external systems.

Keeping these operations inside an HTTP request can lead to long timeouts and poor user experience.

A better design is often:

POST /reports
      |
      v
Create Job
      |
      v
    Queue
      |
      v
Report Worker
      |
      v
Object Storage

The API can immediately return a job identifier while background workers complete the expensive operation.

4. Independent Consumer Scaling

Events allow different workloads to scale independently.

Suppose every completed purchase generates three types of downstream processing:

  • fraud analysis,
  • customer notifications,
  • analytics aggregation.

These workloads may have completely different resource requirements.

Fraud analysis might be CPU-intensive. Email delivery might be network-bound. Analytics may be optimized for batch processing.

Independent consumers allow each workload to scale according to its own needs.

The Price: Eventual Consistency

The largest conceptual change in asynchronous architecture is often not the message broker.

It is consistency.

In a synchronous workflow, an application may update several operations before returning success.

In an event-driven system, downstream state may appear later.

For example:

Order Created
    |
    | 0 ms
    v
Order Database Updated

    |
    | +100 ms
    v
Inventory Updated

    |
    | +500 ms
    v
Search Index Updated

    |
    | +2 sec
    v
Analytics Updated

During that window, different systems can contain different versions of reality.

This is eventual consistency.

For many business processes, that is perfectly acceptable.

For others, it is not.

A payment authorization may need stronger guarantees than an analytics dashboard.

Architecture should reflect those differences rather than applying one consistency model everywhere.

Retries Change Application Semantics

Reliable message processing usually depends on retries.

If a consumer fails while processing a message, the broker may deliver that message again.

This sounds straightforward until the operation has side effects.

Imagine the following message:

{
  "type": "PaymentRequested",
  "orderId": "ORD-10482",
  "amount": 99.00
}

The consumer charges the customer successfully but crashes before acknowledging the message.

The broker delivers the message again.

Without protection, the customer may be charged twice.

This is why asynchronous systems need to assume duplicate delivery can happen.

Idempotency Is Not Optional

An idempotent operation can safely be executed multiple times without changing the final result beyond the first successful execution.

One common pattern is to attach a unique operation identifier to each message.

{
  "eventId": "evt_7f9d82",
  "type": "PaymentRequested",
  "orderId": "ORD-10482"
}

Before performing the operation, the consumer checks whether that event has already been processed.

if event_already_processed(eventId):
    acknowledge_message()
    return

process_event()

store_processed_event(eventId)

acknowledge_message()

The exact implementation varies, but the architectural principle remains the same:

Design consumers as if every message may arrive more than once.

Exactly-Once Delivery Is Usually the Wrong Mental Model

Teams sometimes try to solve duplicate processing by searching for infrastructure that promises exactly-once delivery.

The problem is that delivery guarantees and business side effects are not the same thing.

A message platform may provide strong delivery guarantees inside its own boundaries, but your consumer may also write to a database, call an external API, charge a credit card, or upload a file.

Those side effects live outside the broker.

For that reason, robust distributed systems usually combine infrastructure guarantees with idempotent application logic.

Ordering Is More Expensive Than It Looks

Many event-driven designs begin with the assumption that events will be processed in the same order in which they were created.

Distributed processing can break that assumption.

Event 1: CustomerAddressUpdated
Event 2: CustomerAddressUpdated
Event 3: CustomerDeleted

With multiple consumers, retries, partitions, and network delays, the processing order may differ from the production order.

Before requiring strict ordering, ask whether the business process actually needs it.

Global ordering can severely limit parallelism.

A more scalable approach is often to require ordering only within a logical entity:

  • one customer,
  • one account,
  • one order,
  • one device.

That allows unrelated entities to be processed concurrently.

Dead-Letter Queues Are Part of the Design

Some messages will repeatedly fail.

The reason may be invalid data, a permanent downstream error, an application bug, or an event schema the consumer does not understand.

Retrying forever is rarely useful.

A common pattern is:

Main Queue
   |
   v
Consumer
   |
   | failure
   v
 Retry
   |
   | repeated failure
   v
Dead-Letter Queue

A dead-letter queue is not simply a place where broken messages disappear.

It needs an operational process around it.

Teams should know:

  • who monitors it,
  • what alerts are triggered,
  • how messages are inspected,
  • how data is corrected,
  • how messages are safely replayed.

A dead-letter queue without ownership is just a delayed production incident.

The Dual-Write Problem

One of the most important failure modes in event-driven architecture appears when an application updates its database and publishes an event as two separate operations.

1. INSERT order
2. Publish OrderCreated event

What happens if the database write succeeds but the event publication fails?

The order exists, but downstream systems never hear about it.

Reversing the order creates the opposite problem.

1. Publish OrderCreated event
2. INSERT order

If the event succeeds but the database transaction fails, consumers may process an order that does not exist.

This is the dual-write problem.

The Transactional Outbox Pattern

A common solution is the transactional outbox.

Instead of publishing directly to the broker, the application stores the event in the same database transaction as the business change.

BEGIN TRANSACTION

INSERT INTO orders (...)

INSERT INTO outbox (
    event_type,
    payload
)

COMMIT

A separate publisher then reads the outbox and sends events to the messaging system.

Application
    |
    v
Database
  |     |
Orders  Outbox
          |
          v
      Publisher
          |
          v
     Event Broker

This does not eliminate every failure mode, but it closes an important consistency gap between application state and event publication.

Event Choreography vs Workflow Orchestration

As event-driven systems grow, business workflows can be coordinated in two broad ways.

Choreography

Each service reacts to events and publishes new events.

OrderCreated
    |
    v
Payment Service
    |
PaymentCompleted
    |
    v
Inventory Service
    |
InventoryReserved
    |
    v
Shipping Service

No central component owns the entire workflow.

Choreography can work well for simple workflows with loose coupling.

But long chains can become difficult to understand.

Answering a basic question such as “why has this order not shipped?” may require tracing events across multiple services.

Orchestration

A dedicated workflow component manages the sequence.

             Workflow
            Orchestrator
           /     |      \
          v      v       v
      Payment  Stock   Shipping

The orchestrator knows the current workflow state and tells participants what to do next.

This introduces central coordination but can make complex business processes much easier to reason about.

Do Not Turn Every Service Call Into an Event

Event-driven architecture does not mean synchronous APIs disappear.

Most mature systems use both models.

Synchronous requests are usually appropriate when:

  • the caller needs an immediate answer,
  • the operation is short,
  • the dependency is required to complete the request,
  • strong consistency is important,
  • failure should be visible to the caller immediately.

Asynchronous communication is often more appropriate when:

  • work can happen later,
  • temporary consumer downtime should not block producers,
  • traffic needs buffering,
  • processing is slow or resource-intensive,
  • multiple independent consumers need the same information,
  • consumers need to scale independently.

Queue vs Pub/Sub

Not every asynchronous use case requires a full event streaming platform.

The first distinction is usually between work queues and publish-subscribe systems.

Queue

Producer
   |
   v
 Queue
 / | \
v  v  v
Workers

A queue is usually appropriate when one unit of work should be handled by one consumer.

Examples:

  • resize an image,
  • generate a report,
  • process an uploaded file,
  • send an email.

Publish-Subscribe

       Event
         |
         v
      Broker
     /   |   \
    v    v    v
   A     B     C

Publish-subscribe is useful when multiple independent consumers need to react to the same event.

For example, an OrderCompleted event might be consumed by analytics, loyalty, fulfillment, and notification systems.

When Do You Need an Event Stream?

Some platforms treat messages primarily as transient jobs.

Event streaming systems often treat events as an ordered, retained log that can be consumed and replayed.

That distinction matters when:

  • multiple consumer groups need independent positions,
  • events need long retention periods,
  • historical events need to be replayed,
  • high-throughput data pipelines are required,
  • stream processing is a core workload.

If your requirement is simply “run this background job later,” an event streaming platform may add unnecessary operational complexity.

Use the simplest messaging model that satisfies the actual delivery and processing requirements.

Observability Becomes More Important

A synchronous request often produces a relatively clear trace:

Browser
  -> API
      -> Database
      -> Response

In an asynchronous system, the same business transaction may look like:

API
 |
 v
Database
 |
 v
Outbox
 |
 v
Publisher
 |
 v
Broker
 |
 +--> Consumer A
 |      |
 |      v
 |    Database
 |
 +--> Consumer B
        |
        v
      External API

Logs from a single service are no longer enough.

Useful observability usually requires correlation identifiers carried across the entire workflow.

Important metrics include:

  • queue depth,
  • oldest message age,
  • consumer lag,
  • processing latency,
  • retry rate,
  • dead-letter volume,
  • consumer error rate.

CPU utilization alone tells very little about the health of an asynchronous workflow.

Consumer Lag Is Often More Important Than Queue Size

A queue containing 100,000 messages may be perfectly healthy if consumers process millions of messages per minute.

A queue containing only 500 messages may represent an incident if those messages have been waiting for two hours.

That is why message age and consumer lag often provide better operational signals than raw queue depth.

The key question is not:

How many messages are waiting?

It is:

How far behind the expected processing time are we?

Schema Evolution Becomes an Architecture Problem

Once multiple teams consume an event, changing its structure becomes similar to changing a public API.

Consider:

{
  "eventType": "CustomerCreated",
  "name": "Alex"
}

A producer later changes the event to:

{
  "eventType": "CustomerCreated",
  "firstName": "Alex",
  "lastName": "Smith"
}

Older consumers may still expect the name field.

The event producer cannot safely assume every consumer will upgrade at the same time.

Event schemas therefore need compatibility rules.

Common practices include:

  • adding fields instead of removing them,
  • making consumers tolerant of unknown fields,
  • explicit schema versioning when necessary,
  • maintaining schema contracts,
  • testing producer-consumer compatibility.

Event Replay Is Powerful — and Dangerous

Retained events can sometimes be replayed.

This is useful when:

  • a new consumer needs historical data,
  • a bug caused incorrect processing,
  • an analytics model must be rebuilt,
  • a downstream database needs reconstruction.

But replaying events can also repeat side effects.

Reprocessing a historical EmailRequested event should probably not send thousands of old emails again.

The same applies to payments, notifications, external API calls, and other irreversible actions.

Replayability must therefore be designed at the consumer level.

When Event-Driven Architecture Makes Things Worse

Events are not automatically an architectural improvement.

They can make a simple system significantly harder to operate.

Too Many Small Services

A workflow that could have been a function call becomes a distributed chain across five services.

Invisible Dependencies

With APIs, dependencies are often visible in code. With events, new consumers can appear without the producer knowing they exist.

Harder Debugging

A user-facing failure may originate from an event emitted several minutes earlier.

Operational Overhead

Brokers, consumers, retries, dead-letter queues, schema management, monitoring, and replay mechanisms all need ownership.

Eventual Consistency Everywhere

Teams sometimes accept weaker consistency even where the business does not benefit from it.

Complex Recovery

Recovering from a failed asynchronous workflow may require compensating operations rather than simply rolling back a database transaction.

Distributed Transactions Become Business Workflows

In a single database, a transaction may look like:

BEGIN

Reserve Inventory
Charge Payment
Create Order

COMMIT

Once each operation belongs to an independent service, a single database transaction is no longer available.

The system may instead perform:

Create Order
    |
    v
Reserve Inventory
    |
    v
Charge Payment
    |
    v
Confirm Order

If charging the payment fails after inventory has been reserved, the system needs a compensating action.

Payment Failed
    |
    v
Release Inventory
    |
    v
Cancel Order

This family of patterns is often associated with sagas.

The important architectural shift is that rollback is no longer purely technical. Recovery becomes part of the business workflow.

A Practical Decision Framework

Before introducing asynchronous messaging, ask what problem it solves.

Requirement Request-Response Async Messaging
Immediate result required Strong fit Usually poor fit
Simple CRUD workflow Strong fit Often unnecessary
Long-running processing Risk of timeouts Strong fit
Traffic spikes Requires immediate capacity Queue can absorb bursts
Consumer downtime Can break upstream requests Can buffer work
Multiple independent consumers Requires multiple calls Pub/Sub fits naturally
Strong consistency Easier Usually harder
Debugging simplicity Better Requires stronger observability
Independent scaling Possible Often easier
Replay historical work Requires custom design Natural in some event platforms

Five Signals That You May Actually Need Async Messaging

1. Non-critical downstream failures are breaking user requests

If analytics, notifications, or integrations make core API requests unreliable, those operations may belong outside the synchronous path.

2. Traffic arrives faster than downstream systems can process it

A queue can separate ingestion capacity from processing capacity.

3. Users are waiting for operations that do not need to finish immediately

Background jobs can reduce response latency and timeout risk.

4. Many independent systems react to the same business change

Publishing an event may be cleaner than maintaining a growing list of synchronous integrations.

5. Different parts of the workflow need independent scaling

Separating production from consumption allows each workload to scale according to its own bottleneck.

Five Signs You Are Introducing Events Too Early

1. The system has very little traffic

If everything comfortably runs on a small deployment, scalability alone is not a strong reason to introduce messaging.

2. Every event has exactly one producer and one consumer

That may simply be an API call disguised as an event.

3. Developers cannot explain the failure model

Before adopting asynchronous processing, the team should know what happens when messages are duplicated, delayed, lost, or processed out of order.

4. The business requires immediate consistency

Introducing eventual consistency into a workflow that cannot tolerate it creates unnecessary risk.

5. The architecture exists mainly because a messaging technology is fashionable

Infrastructure should follow requirements.

The existence of an event streaming platform is not itself a reason to redesign application workflows around it.

A Better Migration Path

Moving from synchronous communication to events does not need to happen as a large rewrite.

A safer approach is incremental.

Step 1: Keep Core Transactions Synchronous

Start with the operations where immediate consistency and user feedback matter.

Step 2: Move Non-Critical Side Effects to Background Processing

Email delivery, analytics, report generation, and similar workloads are often good first candidates.

Step 3: Add Idempotency and Retry Handling

Do this before message volume becomes large enough to expose duplicate-processing bugs.

Step 4: Add Dead-Letter Handling and Operational Metrics

Do not wait until production failures prove that they are necessary.

Step 5: Introduce Domain Events Where Multiple Consumers Need Them

Events become more valuable when they represent stable business facts rather than implementation details.

Step 6: Add More Sophisticated Streaming Infrastructure Only When Required

Retention, replay, high throughput, partitioning, and independent consumer groups should be requirements, not assumptions.

A Simple Architecture Is Often a Hybrid Architecture

The choice between synchronous and asynchronous communication is not binary.

A well-designed application may use synchronous APIs for its core transaction and events for everything that can happen afterward.

Client
  |
  | Create Order
  v
Order API
  |
  +--> Validate
  |
  +--> Store Order
  |
  +--> Return Success
  |
  v
OrderCreated Event
  |
  +--> Email
  +--> Analytics
  +--> Fulfillment
  +--> Loyalty

The core user interaction remains simple.

Secondary workloads gain the resilience and scaling advantages of asynchronous processing.

This hybrid model is often more practical than attempting to make the entire application event-driven.

Final Takeaway

Event-driven architecture is not automatically more scalable, more resilient, or more modern than request-response architecture.

It trades one set of constraints for another.

You gain buffering, temporal decoupling, independent consumers, and flexible scaling.

You also gain duplicate delivery, retries, eventual consistency, schema evolution, dead-letter handling, more difficult debugging, and more complex operational recovery.

The right question is therefore not:

Should we use event-driven architecture?

A better question is:

Which part of our system has a reliability, latency, scaling, or coupling problem that asynchronous communication would actually solve?

If there is no clear answer, request-response is probably still the simpler architecture.

If there is, introduce asynchronous communication at that boundary first.

Good cloud architecture is not about maximizing the number of distributed components. It is about introducing complexity only where the requirements justify it.

Add a comment