Beloved by tech giants like Netflix and Amazon, microservices have become the new darlings in modern software development, even though they are more than a decade old. But, despite the benefits, this paradigm is easy to get wrong. So, let’s explore what microservices are and, more importantly, what they are not.
What Are Microservices?
The microservice architecture is a software design approach that decomposes an application into small independent services that communicate over well-defined APIs. Since each service can be developed and maintained by autonomous teams, it is the most scalable method for software development.
Microservices vs. Monoliths
Microservice design is the polar opposite of monolith development. A monolith is one big codebase (“the kitchen sink”) that implements all functionalities. Everything is in one place, and no single component can work in isolation. This means that the application must be tested as a whole.
On the plus side, monoliths are easy to get up and running. Airbnb, to give an example, started with “The Monorail,” a Ruby on Rails monolith. While the company was still small, developers could iterate fast. Making broad changes was easy as the relationships between the different parts of the monolith were transparent.
As a company grows and teams increase in size, however, monolith development becomes more difficult. Soon, the system can no longer fit in a single head — there are just too many moving parts, so things slow down.
Microservices allow companies to keep teams small and agile. The idea is to decompose the application into small services that can be autonomously developed and deployed by tightly-knitted teams.
Benefits of Microservices
The main reason that companies adopt microservices is scalability. Services can be developed and released independently without arranging large-scale coordination efforts within the organization.
A benefit of having a distributed system is the ability to avoid single failure points. You can deploy microservices in different availability zones with cloud-enabled technologies, ensuring that your users never experience an outage.
With microservices, the development team can stay small and cohesive. The smaller the group, the less communication overhead and the better the collaboration.
Amazon takes team size to the extreme with their two-pizza teams. Meaning that a team should be small enough to be fed by two pizzas.
The Freedom to Choose the Tech Stack
With a monolith, language and tech stack options are pretty much set from the beginning. New developers must adapt to whatever choices were made in the past.
In contrast, each microservice can use the tech stack that is most appropriate for solving the task at hand. Thus, the team can pick the best tool for the job and their skills. For example, you can implement a high-performing service in Go or C and a high-tolerance microservice with Erlang or Elixir.
More Frequent Releases
The development and testing cycle is shorter as small teams iterate quicker. And, because they can also deploy their updates at any time, microservices can be deployed much more frequently than a monolith.
Microservices Design Challenges
With so many benefits, it would seem that choosing microservices for a new project is a no-brainer. But microservice design also comes with some tough challenges:
Small: applies both to the team size and the codebase. A microservice must be small enough to be entirely understood by one person. Your microservice is too big if it would take you more than a sprint to rewrite it from scratch.
Focused on one thing: a service must focus on one aspect of the problem or perform only one task.
Autonomous: autonomy allows a team to choose the most appropriate language stack and data model. This usually means that each microservice has its own database or persistence layer that is not shared with other services.
Aligned with the bounded context: in software, we create models to represent the problem we want to solve. A bounded context represents the limits of a given model. Contexts are natural boundaries for services, so finding them is part of designing a good microservice architecture.
Loosely-coupled: while microservices can depend on other microservices, we must be careful about how they communicate. Each time a bounded context is crossed, some level of abstraction and translation is needed to prevent behavior changes in one service from affecting the others.
Independently deployable: being autonomous and loosely-coupled, a team can deploy their microservice with little external coordination or integration testing. Microservices should communicate over well-defined APIs and use translation layers to prevent behavior changes in one service from affecting the others.
When “Microservices” Are Not Microservices
How do you know if you’re doing proper microservice design? If your team can deploy an update at any time without coordinating with other teams, and if other teams can similarly deploy their changes without affecting you, congratulations, you got the knack of microservices.
The surest way of losing the benefits microservices offer is by not respecting the decoupling rule. If we look closely, we see that microservices are all about autonomy. When this autonomy is lost, teams must coordinate during development and deployment. Perfect integration testing is required to make sure all microservices work together.
Even so, exhaustive testing cannot catch all problems. When something breaks, coupled services are hell to debug. And when the problem is found, fixing it isn’t always as easy as rolling back an update.
These are all problems that come with distributed computing. If you’ve ever used a cloud service, you’ll know that spreading services or machines over many geographical locations is not the same as running everything on the same site. A distributed system has a higher latency, can have synchronization issues, and is a lot harder to manage and debug. This highly-coupled service architecture is really, deep down, a distributed monolith, with the worst of both worlds and none of the benefits microservices should bring.
If you cannot deploy without coordinating with another team or relying on specific versions of other microservices to deploy yours, you’re only distributing your monolith.
When Microservices Aren’t the Best Choice
Microservices have not replaced monoliths. Both are valid approaches. In fact, a monolith may be the best choice while the team is still discovering what they are building.
A monolith can feel like a natural starting point for a project as it’s simple to develop, quick to iterate, fast to deploy, easy to debug, and more forgiving of design mistakes. Monoliths can take you far before scalability becomes an issue.
Are Microservices Right for You?
Microservices are the most scalable way we have to develop software. But they are not free lunches. They come with some risks that are easy to run afoul of if you’re not cautious. They are great when the team is growing and you need to stay fast and agile. But you need to have a good understanding of the problem to solve, or you can end up with a distributed monolith.