intermediate 2 min answer Multiple choice

When should a service call another synchronously, and when should it publish an event instead? Give me the deciding test, not a preference.

syncasynccouplingavailability
Pick one
Show the full answer Hide the answer

The deciding test

Does this user action succeed or fail based on this callee's response?

If yes, the call is synchronous, because you need the answer to decide. If no, it is asynchronous, because waiting for it buys nothing and costs you the callee's availability.

Everything else — latency, reliability, team preference — is secondary to that question.

What each choice actually costs

Synchronous makes the caller's availability the product of every callee's. Four synchronous dependencies at 99.9% put a ceiling near 99.6%. Latency is additive, including the tail. And the caller must handle partial failure mid-sequence, which is where sagas and compensations come from.

Asynchronous decouples availability and latency, and buys those with eventual consistency, harder end-to-end debugging, and the need for idempotent consumers, dead letter queues and a replay path. It also moves the failure from "the user sees an error now" to "the user sees success and something goes wrong later", which is sometimes worse.

Worked example

Placing an order: reserve inventory and authorise payment synchronously — if either fails, the order must not be accepted. Warehouse dispatch, confirmation email and analytics asynchronously — none of them changes whether the order is valid, and an analytics outage must never be able to fail an order.

That reduces four synchronous dependencies to two, and the two that remain are the ones that genuinely gate the business outcome.

Why the other options fail

"Async for everything" forces you to accept an order you cannot fulfil and cancel it afterwards. That is a legitimate business choice for some catalogues — deliberate overselling with a compensation policy — but it is a business decision, not an architectural default.

"Whenever the callee might be slow" confuses the symptom with the criterion. A slow synchronous dependency that genuinely gates the outcome needs a timeout, a circuit breaker and a fallback — not to be made asynchronous, which would change the semantics.

"Async internally, sync at the edge" is a style rule, and style rules produce the wrong answer in specific cases.

What a strong answer adds

  • The transactional outbox, because an async event published outside the database transaction is a dual-write bug.
  • The middle option people forget: synchronous request, asynchronous completion — accept, return 202 Accepted with a status URL or a webhook, and complete out of band. That keeps the caller's contract honest while removing it from the critical path.
  • Naming the compensation for the synchronous pair: if payment authorisation fails after inventory is reserved, the reservation must be released.