Ask whether the caller needs an answer

A synchronous call is useful when the current decision requires information from another service. It also makes that dependency part of the request’s latency and availability.

A message is useful when another capability can react independently. Sending it does not mean that processing has already completed.

Messaging moves the failure boundary

A broker can decouple the lifetime of a producer from a consumer, but it does not remove failure. Delivery acknowledgements, duplicate handling, retries and poison messages still require deliberate design.

A consumer should make processing safe to repeat where possible. Retries without idempotency can repeat business effects.

Make the user-visible state explicit

For asynchronous work, define pending, completed and failed states. For synchronous calls, define timeouts and what the caller should do when the result is uncertain.

RabbitMQ and gRPC are not interchangeable labels for the same design. Model the workflow first, then choose the contract and transport.