The divide-and-conquer paradigm has changed the software development landscape dramatically in the last decades, deriving into ideas like: frontend and backend separation, external database, layers—you name it. These ideas allowed developers to focus on specific things and to do them well.
Ascending into the software design field, we have many structural styles that follow the same paradigm, including the popular microservices architecture, which will be today’s topic!
The microservices architecture is a design strategy where an application is composed of a suite of small services. Each of those services deals with a specific functionality and can be developed, built, deployed, and scaled independently. Communication between those components is normally asynchronous and non-blocking. Each of those services can also have access to their own database, and an API Gateway can be employed, creating a single-entry point for the clients.
Microservices architecture has become a very popular design strategy for modern applications, especially for those that need to scale and be highly available.
What it Supports or Enables
An architecture based on microservices can help to decrease the individual complexity of each service, and increase the maintainability of applications. Each microservice usually has its own repository, making it easier to ensure that the many functionalities of the application aren’t directly dependent on code from other services. Each service can also use the frameworks and technologies best suited for the task at hand, without imposing restrictions on other parts of the application.
You can also see reliability improvements in the application. This comes from the fact that, since each service is isolated from the rest, the blast radius of a single service failure is much smaller than in traditional approaches. This isolation also allows developers to update parts of the application separately, empowering continuous deployment techniques.
The microservices architecture can also help optimize the use of resources for deployments at scale, as each service can have a different number of instances running at any time.
In the context of zero-downtime deployments, microservice architectures help overcome common impediments:
- Each microservice can be deployed with a different process. In a monolith, all application modules usually share the same update process.
- Reduced update impact.
- As the components (microservices) of the application tend to have clearer boundaries and well-defined interactions (e.g. REST APIs), it's easier to see where data exchange happens, and as a consequence, it's easier to deal with data schema changes.
- Network latency is increased when using the technique, since a request may hop into different components until it reaches its final destination.
- Proper engineering and monitoring of the interactions between each individual service is important, as well as resilience from network latency spikes.
- Smaller teams and applications will benefit less from the technique, as the overhead will be more apparent.
- Defining the domain for each microservice in a way that doesn’t create a “distributed monolith”.
- The complexity of managing a “distributed state” can be many times higher than a local one.
When to Use
While the microservices architecture helps to reduce local complexity, as each service will be simpler than the whole application, it also introduces a new layer of complexity.
Microservices are a good fit for projects with high scalability and high availability requirements since it allows each microservice to scale on its own and also reduces the blast radius of a single service failure. Another benefit is that each service can have its own development and deployment cadence, allowing for faster development cycles.
Since this architecture also increases the overall complexity of the system, it should not be implemented without careful consideration of the impacts of the additional complexity, especially regarding monitoring and observability. The benefits of microservices are best felt in bigger teams and solutions, where the overhead of the architecture is masked by the benefits it brings. For simpler applications, a monolithic architecture can also be faster to develop and deploy.
Adopting in a Greenfield
Assuming it makes business sense to adopt a microservice architecture, the design phase must define what services will be needed by the solution. You can use domain-driven design to help you identify context boundaries and break up your problem domain into services.
For example, in an e-commerce application, there might be a bounded context for the customer service, which manages customer information, and another for the order service, which manages orders and shopping carts.
Another example might be a social networking application with a bounded context for the user service, which manages user information, and another for the news feed service, which manages news feed items.
You can identify context boundaries by looking at the functionality of your application and identifying areas of responsibility. Identifying context boundaries can be a difficult task, and it is important to get it right.
Adopting in a Brownfield
Today, the conversion of monoliths to microservices is very popular. There are a number of processes for doing the decomposition, so we are focusing on how it impacts deployments.
Independent of the strategy used in the transition, a common ground is the creation of new microservices. Therefore, it's an opportunity to define a new deployment process for the microservices, that can use the techniques previously discussed in our Zero Downtime Deployment series (Canary Deployment, Blue-green deployment and Rolling Update).
It's also advised to define a deployment framework and tooling supporting it since microservices often use the same networking-related code. For an existing monolith application, a common path toward microservices is selecting existing components and dissecting them from the monolith one by one. This strategy is also known as Strangler Fig. You can use the following process as a guideline:
- Identify the component to be moved.
- Identify the connections between the component and the rest of the application.
- Refactor the component interface so that it can be translated into a REST API or an event consumer/producer.
- Refactor the monolith so that it uses an intermediate to access the component. This could be done with the use of a Proxy or Bridge design pattern, for example.
- Create a separate application that will implement and serve the new interface.
- In the monolith, switch the intermediate implementation so that it starts using the new remote microservice.
Sometimes the existing components are not a good option for microservices, or the application doesn't even have clear components to start with. In those cases, the microservice migration is an opportunity to rethink how the application should be decomposed. There are other strategies to use to select which parts of the application would be migrated over to the strangler application, such as migrating the processing of specific types of events or entities.
- The benefits of microservices are best felt in bigger teams and solutions, where the overhead of the architecture is masked by the benefits it brings. For simpler applications, a monolithic architecture can be faster to develop and deploy.
- Microservices are a good fit for projects with high scalability and high availability requirements since it allows each microservice to scale on its own and also reduces the blast radius of a single service failure.
- When adopting this pattern, be aware of increased network latency and the higher complexity of managing a distributed state.
Microservices have become a popular design paradigm in the last few years, but that doesn't mean that it's a silver bullet. It comes with its own set of challenges, and it's important for an organization to be aware of them.
This article was written by João Pedro São Gregório Silva, Innovation Expert and co-authored by Isac Sacchi Souza, Principal DevOps Specialist, Systems Architect & member of the DevOps Technology Practice. Thanks to João Augusto Caleffi and the DevOps Technology Practice for reviews and insights.
Fast-growing tech companies partner with Encora to outsource product development and drive growth. Contact us to learn more about our software engineering capabilities.