There are different strategies here, depending of situation. The most common style of communication is Remote Procedure Invocation (RPI). This includes REST calls, gRPC or thrift. It can be used only when one service calls another. This is known also as request/response. These are synchronous, the response must come in a timely fashion in order to make sure the threads do not block. A service can call another one in a asynchronous way by using notifications, where no response is expected (much like UDP protocol). Another asynchronous way is like ajax request in js, where the caller waits for an answer but will not block. We have also the situation where a service calls multiple services. In this one a publish/subscribe mechanism is the way to go. A queue broker will do the trick here, but this means an extra component to manage. It’s important that the broker must be highly available. Of course we can mix these strategies.
The most common method is REST calls. The services must be designed to respect the Richardson maturity model. In a nutshell it states that a service has 4 levels:
Level 0 API – Clients are calling the service by sending a POST request to the facade endpoint. The request specifies the action, its target and any parameters. This is the old RPC style but communicating through HTTP.
Level 1 API – Individual resources. To perform an action on a resource, a client makes a POST request adding necessary information. The resource is queried before an action is made on it.
Level 2 API – HTTP verbs to perform actions: GET to retrieve, POST to create, PUT to update, DELETE to delete.
Level 3 API – HATEOAS (Hypertext As The Engine Of Application State) principle. A GET request on that resource will reveal all the actions that can be performed on it at the time of the request. The list of actions may differ from time to time. For example, a bucket can be full at a point, so the add operation would not be possible at that point, therefore it will be removed from the list.
Message based communication is also a good alternative. Basically a queue broker is used like activemq, kafka, etc. This ensure asynchronous communication and decouples the client from the service.
Whatever solution you choose your system must be handle partial failure, as the some of the dependent services might fail. There are some guidelines which help dealing with this:
always use timeouts when waiting for responses to ensure that resources are not blocked
setup a threshold of requests a service can handle; makes no sense to produce more that you can consume;
setup fallback for errors; it’s important that the service to remain high available; use cached data, stubs, eventual consistent data
use a circuit breaker; maintain a report of failed/success requests; if that report indicates that the error rate is over a previously setup threshold break the circuit so that subsequent requests will fail immediately; makes no sense to send requests to broken services; Hystrix will help you in achieving this