Microservice Communication Patterns: REST, gRPC, Queues, Pub/Sub, Streaming, and Workflows
Microservices communicate over unreliable networks.
That single fact changes architecture dramatically.
A local function call either returns or throws. A remote call can succeed on the server while the client times out, arrive twice after retries, wait behind a queue, be delivered out of order, or depend on another service that is temporarily unavailable.
Choosing how services communicate is therefore not just a protocol decision. It determines latency, coupling, consistency, failure behavior, scaling, and observability.
This guide compares the patterns that actually matter in production.
Start With the First Decision: Synchronous or Asynchronous?
AWS Prescriptive Guidance groups microservice communication into two broad styles.
Synchronous
The caller sends a request and waits for the result.
Service A → Service B
← response
Typical technologies:
- HTTP/REST
- gRPC unary RPC
- GraphQL between selected boundaries
Use this when the caller cannot continue without an immediate answer.
Asynchronous
The caller submits work or publishes information and continues without waiting for the final result.
Service A → Queue / Broker → Service B
Service A continues
Typical technologies:
- queues
- pub/sub
- event streaming
- callbacks
- workflow engines
Use this when delayed processing is acceptable or when you need to absorb load and reduce availability coupling.
Neither style is universally better.
Pattern 1: REST Request-Response
REST over HTTP remains a strong default for externally exposed APIs and simple synchronous service-to-service communication.
Example:
GET /customers/42
The caller needs customer data before it can continue.
Good fit
- CRUD-style APIs
- public APIs
- simple request-response operations
- broad client compatibility
Advantages
- familiar tooling
- easy debugging
- easy proxy/gateway support
- human-readable payloads when using JSON
Risks
- synchronous dependency on downstream availability
- serialization/network overhead
- long dependency chains amplify latency
Use REST because the interaction is naturally request-response, not because every service needs an HTTP endpoint for every internal event.
Pattern 2: gRPC Unary Calls
gRPC uses service contracts commonly defined with Protocol Buffers and is often attractive for internal RPC.
Conceptually:
rpc GetInventory(GetInventoryRequest)
returns (GetInventoryResponse);
Good fit
- internal service-to-service APIs
- strongly typed contracts
- low-latency/high-throughput RPC
- polyglot systems with generated clients
Advantages
- compact binary serialization
- generated clients/servers
- explicit service contracts
- deadlines and streaming support
Risks
- still synchronous in unary mode
- dependency failures still propagate
- browser/public compatibility may need additional infrastructure
REST vs gRPC is not “slow vs fast.” The more important question is whether you need a public resource-oriented API or a strongly typed internal RPC contract.
Pattern 3: Server Streaming
Sometimes the client sends one request and the service produces multiple responses over time.
Client → request
Server → update 1
→ update 2
→ update 3
gRPC supports server-streaming RPCs, and similar behavior can be implemented with SSE or WebSockets depending on the client environment.
Good fit
- incremental results
- status/progress streams
- AI token streaming
- live server updates
The connection itself becomes a resource, so plan for cancellation, timeouts, backpressure, and reconnect behavior.
Pattern 4: Bidirectional Streaming
In bidirectional streaming, both client and server can independently send multiple messages over one logical call.
gRPC's official model preserves message order within each side's stream while allowing both sides to read and write independently.
Client ⇄ Server
Good fit
- interactive realtime sessions
- collaborative systems
- live telemetry/control
- voice or streaming AI workloads
Trade-offs
- connection lifecycle is more complex
- reconnect semantics matter
- flow control/backpressure matter
- horizontally scaled servers need connection-aware routing/state
Do not use bidirectional streams for ordinary CRUD simply because they are powerful.
Pattern 5: Work Queue
A queue distributes units of work to consumers.
Producer → Queue → Worker A
→ Worker B
→ Worker C
Typically, one logical message is processed by one consumer from a competing-consumer group.
Good fit
- background jobs
- email sending
- image processing
- invoice generation
- import pipelines
- workloads with traffic spikes
AWS guidance notes that asynchronous communication lets services process work according to their capacity and improves peak-load handling.
The queue becomes a buffer between arrival rate and processing rate.
Production concerns
- duplicate delivery
- visibility/acknowledgement semantics
- dead-letter queues
- retry policy
- message age
- idempotent consumers
A queue does not remove failures. It gives you a controlled place to absorb and retry them.
Pattern 6: Publish/Subscribe
Pub/sub is different from a work queue.
One event can be delivered to multiple independent subscribers.
OrderPlaced
↓
Topic
├── Notifications
├── Analytics
├── Loyalty
└── Warehouse
AWS defines pub/sub as a pattern that decouples publishers from interested subscribers through messaging infrastructure.
The publisher does not need to know which subscribers exist.
Good fit
- domain events
- fan-out
- extensible event-driven systems
- multiple reactions to the same fact
Main benefit
Adding a new subscriber does not require modifying the publishing service.
Main risk
Business behavior becomes distributed across subscribers and can be harder to understand end to end.
Pattern 7: Event Streaming
Event streaming keeps ordered event records available for consumers to read and often replay.
Apache Kafka describes event streaming as capturing, durably storing, processing, and routing continuous streams of events.
Conceptually:
Producer → Topic/Partition → durable event log
↑ ↑
Consumer A Consumer B
Unlike a simple queue, consumers can often maintain their own positions and re-read history.
Good fit
- event-driven data pipelines
- CDC
- analytics
- state reconstruction
- high-volume integration
- multiple independent consumers
Trade-offs
- partitioning/order design
- consumer offsets
- schema evolution
- retention/storage
- operational complexity
Use event streaming when durable history/replay is part of the requirement—not merely because you need asynchronous work.
Queue vs Pub/Sub vs Event Stream
They solve related but different problems.
| Need | Better starting pattern |
|---|---|
| One job handled by one worker | Work queue |
| One event fan-outs to many consumers | Pub/sub |
| Durable ordered history + independent replay | Event stream |
| Immediate answer required | REST/gRPC |
| Long multi-step business process | Workflow/orchestration |
Real platforms may combine these patterns.
Pattern 8: Fire-and-Forget
The client submits work and only needs acknowledgement that the work was durably accepted.
POST request
↓
durable queue/job record
↓
202 Accepted + job_id
The important word is durably.
AWS guidance recommends not acknowledging fire-and-forget work until the request has been persisted or placed into a durable queue.
Otherwise:
return success
→ process crashes
→ work disappears
For user-visible operations, provide a status endpoint or event so the client can learn the final result.
Pattern 9: Async Request-Reply / Claim Check
Sometimes work is asynchronous but the caller eventually needs the result.
A common pattern:
POST /reports
→ 202 Accepted
→ job_id
GET /reports/{job_id}
→ queued / running / completed / failed
This is useful for:
- long AI jobs
- exports
- large report generation
- media processing
Polling should use sensible intervals/backoff rather than hammering the service continuously.
Pattern 10: Callback / Webhook
Instead of polling, the caller provides a callback target.
Client → start task + callback URL
Service processes task
Service → callback when complete
Good fit
- cross-system integrations
- long-running external jobs
- payment/provider notifications
Security requirements
Treat callbacks as public APIs:
- authenticate/sign payloads
- validate destinations
- retry safely
- protect against SSRF when accepting URLs
- use idempotent event IDs
Callbacks reduce polling but create delivery and security responsibilities.
Pattern 11: Choreography
In choreography, there is no central workflow controller.
Services react to events and emit more events.
OrderPlaced
↓
Inventory reserves stock
↓
InventoryReserved
↓
Payment captures funds
↓
PaymentCaptured
AWS notes that choreography supports loose coupling and independent service scaling.
Good fit
- loosely coupled reactions
- simple event chains
- events with multiple independent consumers
Risks
- workflow logic becomes scattered
- debugging requires tracing across events
- complex compensation becomes difficult
Choreography works best while the process remains understandable from its event contracts.
Pattern 12: Orchestration
Orchestration uses a central workflow controller.
Orchestrator
├── call inventory
├── call payment
├── wait for result
├── branch on failure
└── compensate if required
AWS guidance recommends orchestration when workflows span multiple microservice boundaries and need centralized logic, branching, compensation, or visibility.
Good fit
- multi-step business transactions
- sagas
- approval flows
- long-running workflows
- explicit compensation
Trade-off
The orchestrator becomes an important workflow dependency, so keep domain decisions in appropriate services rather than turning it into a new monolith.
Choreography vs Orchestration
A useful rule:
Prefer choreography when
- the event is a fact many services may react to
- reactions are mostly independent
- coupling to a central workflow would be unnecessary
Prefer orchestration when
- order of steps matters
- compensation matters
- a single workflow status is useful
- complex branches must be explicit
Many good systems use both.
Data Consistency Changes With the Communication Pattern
Synchronous RPC often gives the caller immediate confirmation of one service's local state.
Async/event-driven systems commonly introduce eventual consistency.
Example:
Order Service: confirmed
Search/read model: still pending for 200 ms
The product must tolerate that delay.
Do not add async architecture to a workflow whose UX requires immediate global consistency without designing how that consistency is achieved.
Idempotency Is Mandatory Around Retries
A timeout does not prove that a remote operation failed.
Client sends CapturePayment
Server captures payment
Response lost
Client retries
Without idempotency, retries can duplicate side effects.
Use stable request/event IDs and enforce uniqueness around business actions.
This matters across REST, gRPC, queues, callbacks, and workflows—not only messaging.
Ordering Is a Business Requirement, Not a Broker Checkbox
Ask which events actually require order.
Example:
OrderCreated
OrderCancelled
may require ordering per order, but not globally across every order in the company.
Global ordering reduces parallelism.
Prefer the smallest ordering scope that preserves correctness, often via a partition key such as:
order_id
customer_id
tenant_id
Backpressure Must Exist Somewhere
When producers create work faster than consumers can process it:
incoming rate > processing rate
something has to happen.
Possible mechanisms:
- queue buildup with bounded retention
- producer throttling
- rate limits
- concurrency limits
- load shedding
- autoscaling consumers
An unbounded queue is only delayed failure.
Monitor both queue depth and oldest message age so you can distinguish harmless bursts from permanently falling behind.
Trace Across Async Boundaries
Distributed tracing is straightforward for synchronous HTTP and harder for asynchronous systems.
Carry trace/correlation metadata in events:
trace_id
correlation_id
causation_id
event_id
Then you can reconstruct:
API request
→ event publish
→ consumer
→ downstream API
→ database
Without this, an event-driven system becomes much harder to debug than a synchronous one.
A Decision Framework
Before choosing a communication pattern, ask:
- Does the caller require an immediate response?
- Can temporary inconsistency be tolerated?
- Is the operation high-volume or bursty?
- Does one message need one consumer or many?
- Must events be replayable?
- Is ordering required, and at what scope?
- Is the workflow multi-step with compensation?
- What happens when the receiver is unavailable?
- Can the operation be retried safely?
- How will operators trace the workflow?
The answers usually make the pattern much clearer.
Common Mistakes
Making everything synchronous
Creates long availability and latency chains.
Making everything event-driven
Creates unnecessary debugging and consistency complexity.
Using Kafka as a job queue by default
A durable event log and a simple work queue have different operational models.
Ignoring idempotency
Retries then become dangerous.
Requiring global ordering
Often destroys useful parallelism for no business reason.
Acknowledging async work before it is durable
Creates silent data loss during crashes.
Hiding workflow state in logs
Long workflows deserve explicit state.
Production Checklist
- Communication style matches business latency requirements
- Synchronous dependency chains are intentionally bounded
- Async work is durably accepted before acknowledgement
- Queue consumers are idempotent
- Event contracts are versioned
- Ordering scope is explicitly defined
- Retries have deadlines/backoff/jitter
- Pub/sub subscribers can fail independently
- Long workflows have explicit status
- Callbacks/webhooks are authenticated and retry-safe
- Backpressure is measurable
- Trace/correlation IDs cross async boundaries
- Choreography/orchestration choice matches workflow complexity
Final Takeaway
Microservice communication should be chosen from the business interaction backward.
Use REST or gRPC when an immediate result is genuinely required. Use queues when work can be processed later by one consumer. Use pub/sub when many independent services need the same event. Use event streams when durable history and replay matter. Use orchestration when a multi-step workflow needs explicit sequencing and compensation.
The best architecture rarely uses one communication style everywhere. It uses several patterns deliberately, with clear failure, retry, ordering, and observability semantics around each boundary.
References
BUILDING A SAAS PRODUCT?
Explore Lofingo’s end-to-end SaaS platforms, multi-tenant cloud architecture, and high-concurrency backend engineering.
EXPLORE SAAS SERVICES →