Sync vs async communication
When to call another service and when to publish an event — and how chatty APIs recreate the monolith over the network.
Communication style is how boundaries either stay healthy or collapse into latency and coupling.
Synchronous calls
Use a request/response call when the caller needs an answer to continue the current user or workflow step: authorize, quote, fetch current status.
Keep those calls:
- few — long call chains amplify latency and failure
- timeout-bound — never wait forever
- idempotent where retries are possible
- versioned so producers can evolve
A chain of five synchronous services is one slow, fragile system wearing microservices clothing.
Asynchronous events
Publish an event when something already happened and others should react: PaymentCaptured, RefundIssued, SettlementFileReceived.
Events decouple release cycles: the publisher does not wait for every consumer. Consumers own their processing pace, retries, and projections.
Good events are about facts in the domain, not “please update table X.” Commands sent as fake events create the same coupling as RPCs with worse debugging.
Choosing deliberately
Ask:
- Does the caller need a result before it can respond or commit?
- Is the other side’s work part of the same user-facing latency budget?
- Would waiting on that dependency turn a partial outage into a full outage?
If (1) is no, prefer async. If (2) and (3) scream “critical path,” keep sync — but design for failure (timeouts, defaults, degraded mode).
Anti-patterns seniors should reject
- Chatty orchestration of many tiny sync hops for one business action
- Shared “message bus” as a database (everyone reads and writes everything)
- Fire-and-forget without delivery and idempotency — async without ownership of failure
- Temporal coupling disguised as async: “we publish, then immediately poll until they finish”
Practical default
Inside a capability, sync is normal. Across capabilities, default to async notification and reserve sync for true queries and commands that must complete in the request path.
The goal is not “use Kafka everywhere.” The goal is controlled coupling: call when you must wait; publish when others can catch up.