Introduction

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 of service-discovery mechanism. When generating requests to a downstream service, we also need a way to distribute traffic across the various instances of the service that minimizes latency and distributes the load evenly without compromising data integrity. We’ll need to consider how to handle service failures and prevent them from cascading throughout our system.

Sometimes a service will need to communicate with other services asynchronously, in these cases, we can use event-driven architectural patterns to create reactive workflows. Breaking our system up into multiple services also means that different services will evolve their APIs independently, so we'll need ways to handle changes that won't break upstream services.

In this chapter, we'll discuss recipes designed to address each of these challenges. By the end of this chapter, you'll be able to confidently handle the various kinds of interactions we're bound to require in a microservice architecture.