In the previous chapters, we've covered how to begin breaking a monolithic codebase into microservices, as well as best practices for exposing your microservices to the public internet. So far, we've assumed that all of our microservices are standalone applications that have no dependencies. These simple microservices receive requests, retrieve data or write to a database, and return a response to clients. This kind of linear workflow is rare in real-world systems. In a real-world microservice architecture, services will frequently need to invoke other services in order to fulfill a user's request. A typical user request will commonly create dozens of requests to services in your system.
Managing the communication between services presents a number of challenges. Before a service can speak to another service, it will need to locate it through some kind...