Skip to main contentSkip to user menuSkip to navigation

Communication Patterns

Learn communication patterns for distributed systems: synchronous vs asynchronous, messaging patterns, and event-driven architecture.

20 min readAdvanced
Not Started
Loading...

What is a communication pattern?

A communication pattern defines how software components exchange requests, messages, and events. It answers four questions before a protocol or product is chosen:

  • Does the sender wait for an answer?
  • Does one consumer or many consumers receive the work?
  • Can the system buffer a traffic burst?
  • What happens when delivery or processing fails?

The pattern is an architectural decision, not a library choice. HTTP can carry asynchronous callbacks, and Kafka can support request-reply, but those tools do not remove the need to choose latency, coupling, delivery, and consistency behavior deliberately.

Caller waits

Request-response

Use when the caller needs an immediate result. The contract is direct, but downstream latency and failure become part of the caller's path.

One worker

Work queue

Use when work can finish later. The queue absorbs bursts and lets workers scale independently, but handlers must tolerate retries.

Many consumers

Event stream

Use when several capabilities react to a fact that already happened. Replay and fan-out improve flexibility while making ordering and schema evolution explicit.

Start with the workload, not the technology

A design becomes clearer when the requirements are expressed as decisions. An interactive price lookup needs a response in the current request. Thumbnail generation can be queued. An OrderCreated event can be retained and consumed by inventory, billing, analytics, and notifications.

Change the constraints below and trace how the recommended path changes.

Interactive decision lab

Choose the communication shape

Change the workload constraints. The path updates to show the simplest pattern that satisfies them.

Does the caller need an answer?
Who receives the message?
How does traffic arrive?

Synchronous path

Request-response

Best fit

The caller waits for one service to return a result.

Producer

Creates intent

Request-response

Worker

Handles work

End-to-end

Latency

Higher

Coupling

None

Buffering

Understand the failure boundary

Every remote interaction crosses a failure boundary. The sender can time out while the receiver still completes the operation, an acknowledgment can be lost, or a broker can redeliver a message after a consumer restarts.

The central distinction is between delivery and business effect:

  • A transport can promise at-most-once or at-least-once delivery.
  • The application decides whether repeated delivery repeats the business operation.
  • An idempotency key lets several attempts converge on one durable effect.

Use the lab to create duplicate deliveries and observe the result.

Delivery semantics lab

Inject a duplicate delivery

Separate the broker's delivery promise from the application's business effect.

Step 1

Choose the delivery contract

Step 2

Configure failure handling

Idempotent handlerDeduplicate by payment ID.
Duplicate deliveries1
No retryRetry storm

Step 3

Trace every attempt

Every attempt applies

Attempt 1: appliedAttempt 2: applied

Broker

2 deliveries

Payment handler

No deduplication

Ledger

2 charges

Compose patterns for real workflows

Production systems rarely use one communication shape everywhere. They combine patterns so each step gets the behavior it needs.

  1. 1

    Synchronous

    Validate the order

    Check price, inventory policy, and identity while the user is waiting. Return a clear success or rejection.

  2. 2

    Atomic boundary

    Commit intent and outbox

    Store the order and an outgoing event record in one database transaction so state and communication cannot diverge.

  3. 3

    Queue

    Process durable work

    Run payment capture, fulfillment, and email with bounded retries, idempotency keys, and dead-letter handling.

  4. 4

    Event stream

    Publish business facts

    Let analytics, recommendations, and audit consumers react independently without joining the checkout latency budget.

Recognize the reusable messaging shapes

Competing consumers

Point-to-point queue

Each work item is handled by one available consumer. Use it for image processing, email delivery, and other jobs where workers can share a backlog.

Independent reactions

Publish-subscribe

Each subscriber receives the event through its own subscription. Use it when several domains react to the same business fact.

Async response

Request-reply with correlation

A request and later response share a correlation ID. Use it for long-running work when the caller cannot keep a connection open.

Multi-step transaction

Saga

Local transactions advance a workflow; compensating actions address partial failure. Use it when one database transaction cannot span every service.

Build resilience into the contract

Retries, timeouts, and dead-letter queues are not generic switches. They change load and correctness, so each one needs a bounded policy.

  1. 1

    Process

    Attempt

    Validate the message, check its idempotency key, and perform the smallest durable unit of work.

  2. 2

    Transient failure

    Back off

    Retry only errors that can plausibly recover. Add jitter and cap both the delay and total attempts.

  3. 3

    Persistent failure

    Quarantine

    Move exhausted messages to a dead-letter queue with the payload, error, attempt count, and correlation context.

  4. 4

    Operator action

    Recover

    Fix the cause, replay deliberately, and verify that idempotency protects already completed effects.

Guardrails that belong in the design

  • Timeouts: derive them from the end-to-end latency budget, not a framework default.
  • Retry budgets: prevent one dependency failure from multiplying traffic across the system.
  • Circuit breakers: stop calls that are unlikely to succeed and preserve capacity for recovery.
  • Backpressure: slow producers or shed optional work before queues grow without bound.
  • Observability: carry correlation and causation IDs across synchronous and asynchronous hops.

Match technology to the chosen contract

Choose the semantic shape first, then select a technology that supports its operational needs.

Direct interaction

HTTP or gRPC

Good for user-facing commands, service queries, and low-latency internal calls. Protect every dependency with deadlines and cancellation.

Durable work

RabbitMQ or SQS

Good for task queues, delayed processing, and competing consumers. Define acknowledgment, retry, and dead-letter behavior explicitly.

Retained events

Kafka or Pulsar

Good for replayable event streams, high-throughput pipelines, and multiple consumer groups. Partitioning defines ordering and parallelism.

WebSockets and server-sent events solve a different problem: keeping a delivery channel open to a client. They are often fed by queues or streams behind the gateway rather than replacing them.

Review the decision before implementation

For every remote interaction, write down:

  1. The caller's latency budget and whether it can proceed without a result.
  2. The number of consumers and whether each needs its own copy.
  3. The ordering scope, retention period, and acceptable consistency delay.
  4. The timeout, retry budget, idempotency key, and poison-message policy.
  5. The overload behavior when consumers fall behind.
  6. The correlation fields and metrics needed to reconstruct one business operation.

The strongest design is usually a deliberate combination: synchronous communication for the smallest immediate decision, a queue for durable work, and an event stream for independent reactions.

No quiz questions available
Could not load questions file
Spotted an issue or have a better explanation? This page is open source.Edit on GitHub·Suggest an improvement