Welcome back. In the previous lesson, you separated commands (“do this”) from events (“this happened”) and documents (“here is data”). That vocabulary matters now: a successful asynchronous redesign does not merely replace HTTP with a broker. It changes when services need one another, what they know about one another, and often what the client is promised.
This lesson redesigns a familiar synchronous checkout-style interaction into asynchronous messaging. You will identify the coupling removed, the coupling that deliberately remains, and the trade-offs you should state explicitly in a Technical Lead interview.
Start with the dependency, not the protocol
Consider a conventional synchronous request chain:
- The client calls
POST /orderson Order service. - Order service calls Payment service and waits for authorization.
- Order service calls Inventory service and waits for stock reservation.
- Order service may call Shipping service and wait for a delivery estimate.
- Only then does Order service return a final result to the client.
Each call may be REST, gRPC, or another request-response protocol. The central issue is not whether the Java client uses a non-blocking API or CompletableFuture. If Order cannot complete its business response until Payment and Inventory respond, it has a synchronous runtime dependency on them.
The latency of a sequential chain is approximately:
At normal load, this might appear acceptable. Under a Payment slowdown, Inventory outage, connection-pool exhaustion, or retry storm, however, one dependency can make the entire request path unavailable. The caller must decide how long to wait, what to retry, and whether an ambiguous timeout means “not processed” or “processed but reply lost.”

The Synchronous vs. async communication across microservices image makes an important distinction. In the top row, the client-facing request remains open across Basket, Ordering, Catalog, and potentially further services. In the middle row, Basket answers the client after its own work, while downstream services communicate through messages. That is the architectural shift we want.
Communication in a microservice architecture - .NET
Read Microsoft’s discussion of asynchronous integration to frame the redesign as a question of service autonomy rather than a RabbitMQ- or Kafka-specific technique.
In the section “Asynchronous microservice integration enforces microservice's autonomy,” read from the warning about synchronous dependencies through the discussion of request chains. Then continue through the paragraph beginning “And finally” to see why a service should often maintain a local copy of data it needs rather than querying another service during its client request. Focus on the distinction between an externally synchronous API and internally synchronous service dependencies.
The coupling in a synchronous call chain
“Coupling” should be named precisely. Saying only “messaging makes services loosely coupled” is too vague for an interview.
| Coupling in the synchronous design | What it means in practice |
|---|---|
| Temporal coupling | Order and Payment must be healthy, reachable, and sufficiently responsive at the same time. |
| Location coupling | Order needs Payment’s endpoint, network route, client configuration, and protocol-level availability. |
| Runtime availability coupling | Payment’s outage or saturation directly threatens Order’s ability to accept an order. |
| Control-flow coupling | Order explicitly controls the immediate cross-service sequence: authorize, then reserve, then continue. |
| Query/data coupling | If Order calls Catalog to obtain product data, it depends on Catalog’s query API for its own response. |
| Failure-propagation coupling | Timeouts, transient errors, and overload tend to propagate back through the call chain toward the client. |
Not every synchronous interaction is wrong. A short request to validate user credentials, for example, can be a justified design if the business outcome genuinely cannot proceed without an immediate answer. The mistake is making every internal collaboration synchronous by default, particularly long-running workflows that span independent services.
A concrete redesign: from “place and confirm” to “accept and process”
Assume this original requirement:
A customer submits an order. The system charges payment, reserves stock, and confirms the order.
A naïve implementation makes Order service coordinate everything within POST /orders. The redesign begins by changing the acceptance boundary.
Step 1: Make the API promise explicit
Order service should do only what it can authoritatively complete within its own boundary:
- validate the request fields it owns;
- create an order locally with a state such as
PENDING_PROCESSING; - durably record that acceptance;
- return an order identifier and an HTTP response such as
202 Accepted.
The response now means:
“Order service has accepted responsibility for processing order
O-4821.”
It does not mean:
“Payment succeeded, inventory is reserved, and shipment is ready.”
This is a business and user-experience decision, not merely a technical optimization. The UI can show “Processing your order,” obtain status from GET /orders/O-4821, or notify the customer later. The client may poll Order service’s own API for status; that is different from internal microservices continually polling one another.
A crucial reliability caveat: the service must not simply update its database and hope a later in-memory publish succeeds. A robust design needs a reliable way to publish the resulting message after the local transaction commits. The transactional outbox pattern addresses that database-and-message gap later in the course.
Step 2: Publish a fact, not an instruction to everybody
After accepting the order, Order service publishes:
{
"eventType": "OrderPlaced",
"orderId": "O-4821",
"customerId": "C-881",
"totalAmount": 129.50,
"currency": "USD"
}
OrderPlaced is an event because Order service has already established the fact that it placed the order. It does not mean “Payment, Inventory, and Shipping, please all do work.” Each interested service or workflow component can react according to its own responsibility.
Step 3: Direct actions to their owners with commands
Suppose this workflow requires payment authorization before stock reservation. A workflow coordinator, sometimes called a process manager or later a saga orchestrator, consumes OrderPlaced and sends:
AuthorizePayment
to Payment service.
That is a command, because Payment is the one business authority that can decide whether payment can be authorized. Payment handles the command in its own transaction and publishes one of these facts:
PaymentAuthorizedPaymentDeclined
After PaymentAuthorized, the coordinator sends:
ReserveInventory
to Inventory service. Inventory then publishes either:
InventoryReservedInventoryReservationFailed
The coordinator observes these outcome events and updates workflow state. It can eventually cause Order service to mark the order CONFIRMED, REJECTED, or CANCELLED.
This is not an arbitrary sequence of messages. It preserves the intent classifications from the previous lesson:
| Message | Type | Logical owner | Meaning |
|---|---|---|---|
OrderPlaced | Event | Order | An order was accepted and created. |
AuthorizePayment | Command | Payment | Attempt payment authorization. |
PaymentAuthorized | Event | Payment | Payment authorization succeeded. |
ReserveInventory | Command | Inventory | Attempt to reserve inventory. |
InventoryReserved | Event | Inventory | Inventory reservation succeeded. |
OrderConfirmed | Event | Order | The order reached its confirmed business state. |
Payment cannot “reject” the historical fact OrderPlaced; it can reject or decline its own payment operation. Similarly, a command being successfully delivered does not prove that its business action succeeded. The later outcome event is what establishes that fact.
Was that message processed? Asynchronous Request-Response Pattern
Watch CodeOpinion’s “Was that message processed? Asynchronous Request-Response Pattern” for a visual contrast between a synchronous order workflow and broker-mediated command handling.
Watch the synchronous chain to see how a billing and warehouse sequence creates temporal coupling and ambiguous partial completion. Then watch the asynchronous redesign, focusing on the distinction between a published order event, a command sent to a specific service, and a targeted reply or outcome consumed by the workflow coordinator.
Two valid asynchronous forms: events and request-reply
Not all asynchronous messaging is fire-and-forget publish-subscribe.
Event-driven workflow
The redesign above uses a combination of:
- events to report established business facts;
- commands to ask one authoritative service to perform work;
- a coordinator that holds the workflow state and decides what command comes next.
This form is appropriate when the workflow can take seconds, minutes, or longer, and the client can receive an accepted status rather than wait for a final business outcome.
Asynchronous request-reply
Sometimes a service needs an eventual direct answer from a specific service but should not hold a synchronous HTTP connection open. It can send a command message with a correlation identifier and receive a reply on a separate response channel.

In the Request-reply pattern for microservices image, ShoppingBasket sends a request for a product’s price and includes CorrelationId = xyz. Pricing processes it asynchronously and returns a response with that same identifier. The correlation identifier allows ShoppingBasket to associate the later response with the original pending request.
This removes the need for the two services to be available in the same instant, but it does not make the result immediate. The caller still needs a pending state, timeout policy, and handling for a response that arrives after the original client has moved on.
Use asynchronous request-reply deliberately:
| Use it when | Prefer events and commands when |
|---|---|
| One known service must provide an eventual answer to one requester. | A fact may have zero, one, or many independent consumers. |
| The requester owns a pending workflow and needs a correlated outcome. | Adding a new consumer should not require changing the publisher. |
| The communication is a directed question or command. | The publisher should not control who reacts. |
A direct reply is not a broadcast event. It is a targeted message that belongs to one conversation.
Remove synchronous query dependencies with local projections
A common incomplete redesign looks like this:
- Order publishes
OrderPlacedasynchronously. - But Order still calls Catalog synchronously for price and product availability.
- It still calls Customer synchronously for delivery preferences.
- It still calls Promotion synchronously to calculate discounts.
The initial command path is shorter, but the core availability problem may remain.
For data that Order needs frequently, Catalog can publish events such as ProductPriceChanged or ProductAvailabilityChanged. Order consumes them and maintains a small local projection containing only the fields it needs:
productId, currentPrice, saleStatus, availableToOrder
Order can then make its acceptance decision using its own database rather than Catalog’s live query endpoint.
This removes direct synchronous query coupling, but replaces it with eventual consistency. The Order projection may briefly lag behind Catalog. Therefore, the design needs a business policy. Examples include:
- accept the order with the price displayed to the customer, then validate it later;
- treat the submitted price as a price quote that expires after a defined period;
- reserve an authoritative price during checkout;
- reject or compensate an order if later validation discovers it is no longer valid.
There is no universal technical answer. The correct choice depends on whether a stale price is tolerable, whether the customer can be charged later, and what legal or commercial commitment the displayed price represents.
What messaging removes, what it changes, and what remains
Messaging does not magically eliminate coupling. It changes its form.
Read Chris Richardson’s Microservices.io pattern summary for a compact statement of messaging’s main benefit and its principal operational cost.
In the “Forces” section, read the runtime-coupling statement. Then scroll to “Resulting context” and read the benefits beginning with loose runtime coupling and broker buffering, followed by the noted complexity of operating a highly available broker.
Use this comparison in an interview:
| Concern | Synchronous HTTP chain | Broker-mediated messaging |
|---|---|---|
| Temporal coupling | Caller and callee must overlap in availability. | Producer can publish while consumer is temporarily unavailable; the broker retains work within configured limits. |
| Location coupling | Caller needs a service endpoint and makes a direct network call. | Producer targets a logical queue, exchange, or topic rather than a consumer instance. |
| Failure propagation | Downstream slowness and failure readily travel back to the client request. | Downstream failure becomes queued work, retry activity, or a workflow state requiring recovery. |
| Client semantics | Often gives an immediate final answer. | Usually gives an acceptance acknowledgement and eventual outcome. |
| Read ownership | Services often query each other’s live APIs. | Consumers can maintain local projections from propagated events. |
| Consistency | Can appear immediately consistent along the request path. | Usually accepts temporary divergence between service-local states. |
| Infrastructure | No broker is required for the call itself. | Broker durability, monitoring, access control, and capacity become production responsibilities. |
Coupling that remains
An honest design explicitly names the coupling that remains:
-
Contract coupling
Producers and consumers must agree on event meaning, schema evolution, identifiers, and versioning. This is an intentional, managed coupling. -
Business-semantic coupling
Payment and Inventory still participate in the same customer journey. Messaging does not erase the business relationship; it prevents it from being a single synchronous runtime call stack. -
Workflow coupling
If payment must precede stock reservation, that ordering rule still exists. A coordinator makes the rule visible rather than hiding it in nested HTTP calls. -
Operational coupling to the broker
Broker availability, retention, queue depth, consumer lag, dead-letter handling, and credentials become critical. The broker is not a free reliability upgrade. -
Delivery-semantic coupling
A consumer may receive a message more than once after retries or failures. Consumers must eventually be idempotent, and workflows must tolerate delayed or duplicate messages.
The goal is not “zero coupling.” The goal is to remove unnecessary direct runtime coupling while preserving explicit, governable contracts and business rules.
Avoid the common “asynchronous” redesign mistakes
Returning early but keeping internal synchronous calls
An API that returns 202 Accepted but launches a background thread that synchronously calls Payment, Inventory, and Shipping has decoupled the browser connection, not the microservices. The background worker still suffers from the same dependency chain and failure propagation.
Replacing messages with repeated HTTP polling
The bottom row of the first image labels internal HTTP polling as “asynchronous” because the caller is no longer waiting in one original request. But each poll remains a synchronous network call, still requires the remote endpoint, and can create unnecessary load. Brokered messaging gives stronger temporal decoupling because the broker stores work until a consumer is ready.
Broadcasting commands
Publishing ReserveInventory to many independent consumers is ambiguous. Inventory must be the single logical owner of reservation. Broadcast a fact such as OrderPlaced; direct a command such as ReserveInventory.
Treating accepted work as completed work
A successful publish or a 202 Accepted response proves only that the system accepted the request at a defined boundary. Payment may still decline, inventory may be unavailable, and the workflow may need a compensating action. The final state must be observable.
Publishing an event before the local fact is true
Do not publish OrderConfirmed merely because confirmation was requested. Publish it only once Order has committed that status in its own authoritative state. Reliable publication after local commit is central to later CDC and transactional-outbox lessons.
A concise Technical Lead answer structure
When asked to redesign a synchronous service chain, present the design in this order:
-
State the problematic dependency
“Order currently cannot respond until Payment and Inventory are simultaneously available, so their latency and failures directly affect order acceptance.” -
Define the new acceptance boundary
“Order validates and records its own pending order, then returns an order identifier and an accepted status. It does not claim final confirmation yet.” -
Name messages by intent and owner
“Order publishesOrderPlaced. A coordinator sendsAuthorizePaymentto Payment andReserveInventoryto Inventory. Each service publishes outcome events it authoritatively owns.” -
Explain client completion
“The client observes the order’s eventual state through an Order-owned status endpoint or notification, rather than waiting through a cross-service HTTP chain.” -
State the trade-offs
“This removes direct temporal and location coupling, and allows buffering during downstream outages. In exchange, we accept eventual consistency, pending states, duplicate-delivery handling, broker operations, and explicit workflow recovery.”
That answer shows architectural judgment rather than simply naming Kafka or RabbitMQ.
The central redesign move is to separate accepting an order from completing its distributed business workflow. Brokered messages remove direct temporal and endpoint coupling between services, while local projections can remove synchronous query dependencies. They do not remove contract, business, operational, or delivery-semantics responsibilities; those must become explicit.
Next, you will define an event contract that carries the business fact and the metadata consumers need to correlate, trace, and safely interpret it.
Can't find a good explanation? Sign up and we'll make it for you
Sign up