Inter-Service Communication
How services talk — synchronous REST and gRPC vs asynchronous messaging, the temporal-coupling trade-off, and choosing the right style per interaction.
Microservices have to talk to each other, and the style you choose shapes resilience, latency, and coupling. Synchronous calls (REST, gRPC) give an immediate answer but couple services in time. Asynchronous messaging decouples them for resilience and scale at the cost of eventual consistency. The skill is matching the style to each interaction rather than picking one globally.
The caller sends a request and blocks until the response arrives. REST over HTTP/JSON is the universal default — simple, debuggable, broadly compatible. gRPC uses HTTP/2 and binary Protobuf for compact, low-latency, strongly-typed calls (with streaming). Both require the callee to be available right now.
The sender publishes a message or event to a broker and returns immediately, without waiting for the receiver. The receiver processes it whenever it can. Sender and receiver are decoupled in time — either can be down, restart, or run at a different speed without breaking the other.
Synchronous calls create temporal coupling: a chain of services must all be up and fast, so one slow or failed service can cascade into the whole request. Async messaging removes that coupling and adds resilience and load leveling — but introduces eventual consistency, since the work happens later and the sender doesn't get an immediate result.
It's not one global choice. Use synchronous calls when the caller genuinely needs an answer to proceed (a query, a validation). Use asynchronous events for notifications, workflows, and anything where 'eventually' is fine. Most mature systems mix both, leaning on async to decouple and sync where an immediate answer is required.
Key Concepts
Request/response where the caller blocks until it gets a reply (REST, gRPC). Simple but temporally coupled.
Fire-and-forget messaging via a broker; sender doesn't wait. Decoupled and resilient, but eventually consistent.
HTTP + JSON, resource-oriented. The universal, human-readable default for service and public APIs.
Contract-first RPC over HTTP/2 with binary Protobuf — compact, fast, typed, supports streaming.
When services must be up at the same time for an interaction to succeed — a property of sync calls.
The result of async work — state converges over time rather than being immediately visible.
1// SYNC — REST: simple, blocking, both services must be up2const res = await fetch(`${ORDERS}/orders/42`); // caller blocks here3const order = await res.json();45// SYNC — gRPC: contract-first, binary, fast, supports streaming6service OrderService { // orders.proto7 rpc GetOrder(GetOrderRequest) returns (Order);8 rpc WatchOrders(Empty) returns (stream Order); // server streaming9}10const order = await client.getOrder({ id: 42 }); // generated, typed stub1112// ASYNC — messaging: fire-and-forget, decoupled, resilient13await bus.publish("OrderPlaced", { orderId: 42 }); // returns immediately14// ...elsewhere, independently, possibly minutes later:15bus.subscribe("OrderPlaced", async (e) => { await ship(e.orderId); });
Communication style is one of the highest-impact decisions in a distributed system — it determines how failures propagate and how the system scales. It's a constant interview theme (REST vs gRPC vs queues, sync vs async) and a frequent source of real production fragility when teams default to deep synchronous chains.
Common Pitfalls
1The Synchronous Call Chain That Cascades
Placing an order calls payment, which calls fraud-check, which calls a partner API — all synchronously. When the partner API slows down, the latency stacks up the whole chain and order placement times out for everyone.
A deep synchronous chain means the slowest, least reliable link determines the success and latency of the entire operation. Temporal coupling turns one degraded dependency into a system-wide failure.
Keep only the truly required check synchronous (e.g. payment authorization) and move the rest to async events: publish 'OrderPlaced' and let fraud-check, partner notification, and fulfillment react independently via messaging. The user's request returns fast; downstream slowness no longer blocks it.
Takeaway: Long synchronous chains are fragile — latency and failures compound. Reserve sync calls for what the caller truly needs now, and decouple the rest with async events to contain slowness and failure.
2Choosing gRPC for Hot Internal Paths
An internal recommendation service is called thousands of times per second by other services. Using REST/JSON, serialization overhead and HTTP/1.1 connection churn are measurable in latency and CPU.
Verbose JSON and HTTP/1.1 add real cost at high call volumes between services, and the loose contract invites mismatches between client and server payload shapes.
Switch the internal call to gRPC: a shared .proto contract generates strongly-typed clients/servers, Protobuf shrinks payloads, and HTTP/2 multiplexes many calls over one connection. Latency and CPU drop, and the contract is enforced at compile time.
Takeaway: For high-volume internal service-to-service calls, gRPC's binary protocol, HTTP/2 multiplexing, and contract-first typing beat REST. Keep REST for public, low-volume, or broadly-consumed APIs where simplicity and compatibility win.
Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.