A Look at Microservices

Let's have a look at microservices.

We'll cover the following

Microservices

Applications naturally grow and become too complex to manage easily. The impulse to break large applications into smaller, independent ones has been around a long time. Microservices are currently the most popular approach for this. However, long before microservices, there was SOA (service-oriented architecture).

Oftentimes, we need to break up larger functions into smaller ones with intention-revealing names, and then compose those functions back together to re-create the original behavior. We get the bigger picture from the recomposition, and we can dig into the details by looking at the individual functions. The same goes for breaking up large applications into services.

The key to this recomposition for services is communication. Since services are separate, they need an external mechanism to talk to each other. Most often, this is an HTTP request from one service to another. However, it can also be a message queue like RabbitMQ or a streaming log like Kafka.

This separation—and need for communication—is exactly the source of all the benefits as well as all the problems associated with breaking an application into services.

Let’s take a closer look at both sides of the microservices coin.

Advantages

  • The forces that push developers to reach for services are direct and easy to enumerate—the need for focus, encapsulation, and scale.

  • Unix taught us the virtue of doing one thing and doing it well. A code segment that is smaller and more focused is easier to write and maintain. Unix also taught us that we can compose smaller commands together to build more complex behavior. These same ideas can also be applied to services.

  • Well-designed services hide all their data and implementation details behind a public interface. This allows us to change the implementation behind the scenes as much as we need as long as we preserve the contract of the interface.

  • Different parts of a monolithic application often have very different resource needs. By breaking applications up into services, we can address those needs individually on a service-by-service basis.

Disadvantages

The benefits services provide come at a cost, and that cost can be high.

  • Development, testing, and deployment all become more complex. Handling failure when a service crashes or loses communication over the network takes extra thought, preparation, and work.

  • Development and testing pose similar problems. When services depend on one another for the full application to work, we either need to start all the services up for the system to work, or we need to provide mock services instead.

  • If we choose to use live services, we need to keep the versions up to date. If we choose mock services, we need to make sure they haven’t drifted and still accurately represent the real service.

  • Deployment is vulnerable to the versioning problem as well. Managing version changes across multiple services requires careful planning. Handling breaking changes to one service means we’ll need to update others that depend on it. That often leads to multiple deployments at once to keep the whole system consistent.

  • We also need a new strategy for cross-service fault tolerance—how we keep other services running when one of the services is down or unavailable. A circuit breaker pattern is one way of ensuring this.

  • Circuit breakers monitor external calls for failure or timeouts. They keep the whole system from failing by providing predetermined responses instead of crashing. They may also implement retry strategies to determine when a failed service comes back.

  • One of the biggest disadvantages is determining where to break a monolith apart. Unlike object-oriented (OO) design and refactoring, we don’t have a well-defined language of patterns we can rely on to make these decisions yet.

  • In OO design or refactoring, we have language-level structures appropriate for the job, such as classes, methods, modules, and functions. However, when we break a monolith into services, most languages don’t have constructs at the scale needed to hold a service.

Other languages and ecosystems offer solutions to these problems, but the solutions often add complexity and friction to our workflow. In the Erlang and Elixir ecosystems, OTP provides solutions without the extra overhead.

Get hands-on with 1400+ tech skills courses.