Every service can talk to every other service. Nothing about that fact tells you HOW. Sync REST, async events, choreography, orchestration — the pattern-language that decides whether your system is a graceful microservices architecture or a distributed monolith in disguise. Phase G's finale — the vocabulary that binds the six preceding modules into one design conversation.
You have services. You have gateways (M.33), a mesh (M.34), discovery (M.35), tracing (M.36), and stores (M.37). Now they need to actually exchange information — and the choice of HOW is the single largest determinant of whether the resulting system feels like microservices or feels like a monolith spread across processes. The naive default is "REST over HTTP for everything" because it's what the team already knows. It works. It also silently produces some of the most expensive architectural mistakes in the industry: sync-cascade P99 amplification, fire-and-forget for operations that needed a reply, chatty request-response coupling that prevents independent deploys, orchestrator god-services accreting every workflow's business logic. The pattern language exists precisely because these mistakes aren't obvious until they're expensive to fix. This module is the vocabulary that prevents them.
inventory.reserve() → payment.charge() → shipping.schedule(); if step 3 fails, step 2 must refund; if step 2 fails, step 1 must release. This is the saga pattern (M.30 callback), and it needs an explicit coordinator to track progress and run compensations. If REST is used naively without saga logic: partial failures leave inconsistent state; charges without shipments; the classical distributed transaction problem.// FIT: orchestration · Temporal / Step FunctionsOf six real inter-service scenarios, only ONE is naturally sync request-response. The other five have distinct shapes — fire-and-forget, pub-sub, orchestrated workflow, batch, streaming — each with its own optimal pattern. Defaulting every scenario to sync REST doesn't just give suboptimal performance; it produces specific architectural failures: coupling that prevents independent deploys, distributed monoliths, timeout loops, and eventually consistent systems with none of the guarantees eventual consistency requires. The pattern-language exists to prevent this.
The counter-argument — "we'll just be careful about which pattern to use" — is real but insufficient. The specific patterns exist because they encode failure semantics that ad-hoc "careful" REST can't reproduce. A message queue handles retry, backpressure, and consumer-scaling as first-class primitives; you don't rebuild those correctly with careful REST. A workflow engine handles compensation, checkpointing, and long-running state as first-class primitives; careful REST can't reproduce them without becoming a workflow engine. Each pattern is a set of decisions about failure that has been made well enough that you shouldn't remake them. The lab in §04 lets you feel exactly what each pattern excels at and what happens when you use the wrong one. But the frame stays: every service pair has a specific communication shape that fits its workload, and choosing that shape deliberately is the actual work of microservices architecture.
The composite point that ties Phase G together: M.32 taught you where service boundaries go; M.33-M.37 taught you the operational infrastructure that makes services work; M.38 teaches you how they exchange information across those boundaries. The bounded contexts from M.32 tell you WHAT services exist. The mesh from M.34 makes sync HTTP calls between them safe. The tracing from M.36 makes their behavior observable. But none of those modules answer which communication pattern each service pair should use for each interaction. That answer lives here — and it's the difference between a distributed system that reads like sheet music (each pattern chosen deliberately, playing its role) and one that reads like static (every service calling every other service in whatever way seemed convenient at the time). Phase G's finale is the pattern-language that makes the rest of the phase coherent.
Every inter-service interaction sits somewhere on two orthogonal axes. Axis 1: does the caller wait for a reply? — sync vs async. Axis 2: how many recipients? — 1:1 vs 1:N. Those two questions produce four quadrants, and each quadrant is served by a distinct family of technologies with distinct failure semantics. Getting the axes right for a given interaction is more consequential than any specific technology choice within a quadrant. The 2×2 is the vocabulary; the technologies are the products of it.
Caller sends a request; blocks awaiting a reply from one specific service. The reply IS the point — the response contains the answer the caller needs to continue. Failure modes: timeout, connection refused, non-2xx response, retry-with-backoff. The service mesh from M.34 lives here — mTLS, retries, circuit breakers, load balancing all operate on sync request-response.
Caller sends parallel requests to N services; waits for all (or a quorum); aggregates the responses into one reply. Rare as a primary pattern; common inside gateways and BFF layers (M.33 callback — the BFF fans out to many backend services and returns a composed response). Failure modes are worse: any of N services can fail; partial-completion semantics need explicit design.
Producer drops a message onto a queue; a single consumer picks it up and processes it. Producer doesn't wait; producer doesn't care when it happens. The message broker guarantees durability (the message survives producer death), backpressure (queue absorbs bursts), and often at-least-once delivery. Callback to Phase F: this is where work handoff between services actually lives.
Publisher emits an event to a topic; N consumers each subscribe independently. Publisher doesn't know or care who's listening. Adding a new consumer requires no producer change. The whole M.26-M.31 event-driven story lives here — event-carried state transfer, CQRS read models, CDC pipelines (M.37 callback). The most powerful pattern for decoupled coordination at scale; also the most abusable.
The failure semantics differ in ways that matter. Sync patterns propagate failure — if service B is down, service A's call fails, and A must decide what to do (retry, fallback, error to caller). Async patterns absorb failure — if consumer C is down, the message sits in the queue until C is back; the producer doesn't know or care. This isn't a bug in sync or a feature of async; it's a deliberate architectural difference. Sync couples the caller's fate to the callee's; async decouples them via the durability of the broker. Choosing sync when async fits produces fragile cascading systems; choosing async when sync fits produces UX where users can't tell if their action succeeded. Neither mistake is retrievable without rewriting the interaction.
Two things about the 2×2 that show up in practice. Most systems need all four quadrants — a real e-commerce architecture uses sync request-response for checkout (Q1), scatter-gather in the BFF layer (Q2), fire-and-forget for post-purchase notifications (Q3), and pub-sub events for inventory / pricing / analytics coordination (Q4). "We use REST for everything" or "we use Kafka for everything" are both symptomatic of undertreated architectural decisions. The technology choice within a quadrant is often less consequential than choosing the right quadrant — gRPC vs REST is a real decision (perf, streaming, tooling), but both belong in Q1; if you needed Q3 or Q4, that decision matters more than the Q1-internal one. Getting the quadrant right for each interaction is the first-order design work; picking the technology within a quadrant is the follow-up.
The 2×2 covers point-to-point interactions. But a lot of business processes are multi-step workflows — order fulfillment spans inventory reservation, payment charge, shipping scheduling, and notification delivery. Each step depends on the previous; failure at any step must trigger compensation (release inventory if payment fails; refund payment if shipping fails). The workflow-coordination question — who owns the workflow's execution? — has two answers, and choosing between them is one of the higher-leverage architectural decisions in a microservices system. Choreography vs orchestration is a shape of thinking, not just a technology.
Each service reacts to events autonomously. checkout-svc emits order-placed; inventory-svc consumes it and emits stock-reserved; payment-svc consumes that and emits payment-succeeded; shipping-svc consumes that and emits shipment-scheduled. No central coordinator; the workflow is emergent from the event chain. Compensation events flow the other way on failure: payment-failed triggers inventory-svc to release stock.
A workflow engine (or dedicated orchestrator service) explicitly directs each step. workflow.start(order) → inventory.reserve() → payment.charge() → shipping.schedule() → notify(). If any step fails, the orchestrator runs registered compensation logic in reverse. The workflow is a first-class artifact — you can read it as code, replay it from history, visualize its state.
The composite advice is the same one you'd give about databases (M.37) or samplers (M.36): most real systems mix both, and mix them deliberately. Choreography for genuinely decoupled event flows — inventory-updated triggers pricing recalculation, search reindexing, and analytics rollup independently. Orchestration for multi-step transactional workflows — the order saga, the loan approval, the KYC verification pipeline where sequence, compensation, and auditability matter. The choice is about how much explicit workflow shape the interaction has: if there IS a workflow (defined sequence, defined compensations, defined states), an orchestrator captures it. If there ISN'T — just services reacting to interesting things — choreography stays lighter.
The tooling landscape has clarified significantly. Temporal (formerly Cadence, from Uber) is the dominant open-source workflow engine — you write workflows as regular code (Java, Go, TypeScript, Python) with strong durability guarantees; the engine handles retries, timeouts, and compensations. AWS Step Functions is the leading managed alternative, especially strong for AWS-native architectures where the "activities" are Lambda functions or other AWS services. Camunda / Zeebe comes from the BPMN world and appeals to organizations with formal process modeling. For choreography, the "engine" is just Kafka (or NATS, or your event bus of choice) with idempotent consumers. The technology choice is subordinate to the coordination-model choice: pick your model first (choreography vs orchestration vs mixed); pick the specific tools second. Also worth calling forward: M.30's saga pattern comes in both flavors — choreographed sagas (each service handles its part in response to events) and orchestrated sagas (a saga coordinator explicitly directs each step). Both are valid; both trade off differently.
Below: four common inter-service scenarios from a real e-commerce system — checkout submit, post-purchase email, recommendation update, multi-step order saga. Toggle the communication pattern — sync request-response (services call each other directly and wait for replies), choreography (services react to events on a shared bus), orchestration (a workflow engine directs each step explicitly). Then pick a scenario and see how each pattern performs. Watch which pattern excels at which scenario, which pattern turns simple interactions into distributed monoliths, and which combinations are just plainly wrong.
Every microservices system that fails eventually shares one property: the services can't be deployed independently anymore, because their communication choices coupled them tightly enough that changing one breaks others. This is the "distributed monolith" — a monolith with all the operational complexity of microservices and none of the deploy-independence benefit. The five patterns below are the specific communication mistakes that produce it. Recognizing them in a design review is where architectural maturity actually shows up.
Sync request-response chains multiply latency in a specific pathological way at high percentiles. If each of 10 hops has independent P99 = 100ms (meaning: 1% of calls exceed 100ms), the probability that ALL 10 stay under their individual P99 is only 90.4% — meaning the cascade's P99 is not 100ms but ~200-400ms depending on distribution. Every additional sync hop amplifies P99 more than P50. The fix isn't "make each service faster"; it's cut sync hops — use async patterns where the caller doesn't need the immediate reply, use scatter-gather (parallel) instead of chains (serial), use CQRS-style read models (M.29 callback) to pre-compute answers.
Async patterns hide failure from the caller — that's the point. If the caller needs to know whether the operation succeeded (charge card, place order, submit form), fire-and-forget silently converts operational failures into user-visible incorrectness. The user sees "order placed" because the message was queued successfully; ten minutes later, the payment fails and no one knows to tell the user. The fix: sync for operations where the caller needs the answer within its own timeline; async for operations where the caller genuinely doesn't. "Genuinely doesn't" is the phrase that requires honesty about UX contracts.
Event streams are excellent for "here's what changed"; they're clumsy for "what is true right now." A pure event-driven system where consumers must build local materialized views of every source's state accumulates complexity fast: every consumer becomes a mini-database, replays are expensive, first-boot bootstrap is a coordination problem. The fix: mix. Events for state changes; query APIs or CDC-derived read models (M.37 callback) for "what is true right now." Not every state read needs to be reconstructed from an event replay.
Orchestrators are useful for capturing multi-step workflows; they become anti-patterns when every workflow ends up in one. The pattern accretes because "just add another step" is cheaper than "extract a new orchestrator" — until the god-orchestrator owns 40 business processes and every change is high-risk. The fix: one orchestrator per business process (order-fulfillment orchestrator ≠ subscription-renewal orchestrator ≠ refund orchestrator); resist the "one workflow engine to coordinate all workflows" temptation. Small orchestrators are like small services — the boundaries matter.
When services synchronously call each other for every operation, they've regained the coupling of a monolith without the deployment simplicity. Signal: a schema change in service A requires simultaneous deploys of B, C, D, and E because they all sync-call A with a shared contract. The fix: enforce loose coupling via async events where possible; version the sync contracts explicitly (M.33 callback — the gateway helps here); prefer choreography for cross-boundary state notifications so consumers can evolve independently. The "distributed monolith" isn't a design pattern; it's what happens by default when nobody makes deliberate coupling decisions.
The composite lesson closes Phase G: microservices architecture is not the absence of monolithic coupling — it's the deliberate management of it. M.32 taught you where boundaries go. M.33-M.37 taught you the operational infrastructure. M.38 is where the coupling actually happens — every inter-service interaction is a coupling decision, and the accumulated choices are what determines whether your architecture works. Systems that treat communication patterns as first-class design decisions ship microservices successfully. Systems that treat them as implementation details ship distributed monoliths. The vocabulary in this module is what makes the first path visible.
Two closing observations before Phase G ends. The five anti-patterns are not exotic failures — they're the specific ways real teams accumulate technical debt in distributed systems, and every mature engineering organization has had at least three of them at some point. Recognizing them early — in design reviews, RFCs, architecture discussions — is worth more than any specific technology adoption. The pattern-language is durable: sync request-response, fire-and-forget, pub-sub events, choreography, orchestration — these aren't going to be renamed or replaced by the next framework. Specific technologies within them will churn (REST → gRPC, RabbitMQ → Kafka, custom orchestrators → Temporal), but the shapes stay. Learning the shapes is the actual work; picking technologies within them is the follow-through. Phase G closes here with the vocabulary that makes the rest of the microservices world make sense.
The terms every "sync or async?" design review, every "should this be an event?" RFC, every "orchestration or choreography?" architecture discussion assumes you already know.
Test the pattern-language intuition. Click an answer; explanation drops in instantly.
Perfect. The 2×2 of communication, choreography vs orchestration, the distributed-monolith warning signs — all yours. Seven modules of Phase G assembled into a working pattern-language. Phase H opens with the shift from services to streams.
The pattern-language that binds Phase G — and the microservices architecture — into a coherent whole.
Sync/async × 1:1/1:N produces four quadrants — request-response, scatter-gather, fire-and-forget, pub-sub — each with distinct failure semantics. Most systems need all four; choosing deliberately per interaction is the actual work.
Choreography for genuinely decoupled event flows; orchestration for multi-step transactional workflows with sequence and compensation. Most real systems mix both. The choice is about how much explicit workflow shape the interaction has.
Without deliberate communication choices, microservices systems accumulate tight sync coupling until they're monoliths with worse operational overhead. The five anti-patterns are the natural drift; the pattern-language is the discipline that resists it.
Seven modules. One pattern-language. The operational discipline of microservices, from where the boundaries go to how the services actually talk.
Phase G taught the operational discipline of microservices — not the theory of small services, but the pattern language that binds them into working systems. Bounded contexts (M.32) tell you WHERE services exist. Gateways (M.33) mediate the outside world. The mesh (M.34) makes sync HTTP safe. Discovery (M.35) is how they find each other. Tracing (M.36) is how they explain themselves. Polyglot persistence (M.37) is how each stores what it owns. Inter-service communication (M.38) is how they actually talk. Each is essential; none is optional. The gap between "we have microservices" and "microservices work for us" is exactly this seven-part vocabulary. Every organization that has microservices working well has this vocabulary internalized. Every organization struggling with them is usually missing three or four pieces of it.