A microservices-based application is a distributed system and must use an inter-process communication mechanism. There are two styles of inter-process communication.
A very clear scenario that demonstrates this communication between microservices is an eCommerce application that contains Microservices for Orders, Payments, Inventory, and Shipping receives a new order from the user and is trying to process that order.
Asynchronous messaging (indirect)
Asynchronous messaging can be brokered or broker-less. A message broker could lead to business logic leaking from the services into the broker and is against domain driven design and microservice design principals (dump pipes, smart consumers). Broker-less messaging, such as RabbitMQ, are typically used for publishing events that can be consumed by multiple subscribers.
(Order Service) ---> [Order Received] |--> (Payment Service) |--> [Payment Processed] |--> (Inventory Service) |--> [Stock Changed] |--> (Shipping Service) |--> [Pickup Request]
Order service publishes a message to signal a new event when a new order is received in the system. 3 different services receive a copy of that message and react differently. Payment service starts processing the credit card payment, inventory service reserves the stock quantity required for the order, and shipping service creates a pickup request for the package be shipped to the customer. all events happen simultaneously without direct dependency between the 4 services. this is also a great example of decoupling.
Synchronous messaging (direct)
The other style of inter-process communication is synchronous mechanism such as HTTP. Where services are communicating directly with each other.
Using the same scenario above:
(Order Service) ---> [Process Order] |--> (Payment Service) |--> [Payment Processed] (Order Service) ---> [Process Order] |--> (Inventory Service) |--> [Stock Changed] (Order Service) ---> [Process Order] |--> (Shipping Service) |--> [Pickup Request]
Another format for the same example could be
(Order Service) ---> [Process Payment] |--> (Payment Service) |--> [Payment Processed] (Order Service) ---> [Reserve Stock] |--> (Inventory Service) |--> [Stock Changed] (Order Service) ---> [Ship Order] |--> (Shipping Service) |--> [Pickup Request]
Order service calls each dependant service to invoke their respective functions in order to fulfil a new order. 3 different services receive a direct call as a result of 1 single operation by the user. all calls are synchronous and explicitly sequenced in the desired order and priority.
This scenario tightly controls the orchestration of workflow between Microservice boundaries. But it also tightly couples the Microservices with direct dependancy on each other.
A microservice application typically use both asynchronous and synchronous communication patterns for different purpose. It might even use multiple implementations of each style.
Synchronous (blocking) communication are useful when completing a request is dependant on the results of a sub-request from another service that must be awaited. While asynchronous communication is great for raising events to signal something already happened in case other services want to react to, irrespective of any results of such reactions.