Microservices are becoming one of the more popular architectural styles. As with any architectural choice, there are trade-offs to consider. Before deciding to adopt microservices, one should make an informed choice by weighing the benefits against the drawbacks in the context of the business and technical problems being addressed.
In this article, I am going to present a brief overview of some of the benefits and drawbacks to be considered before settling on a microservice architecture.
In business-related scenarios, software development is about adding business value, not about adopting technology for the sake of the technology. Sam Newman, a respected author and speaker on microservices states this clearly:
“You don’t win by doing microservices. The only person that wins if you do microservices is me because I sell books about microservices. You’re implementing microservices to achieve something. What is it you’re actually trying to achieve? It’s amazing how many people I chat to who can’t give me a clear articulation of why they’re doing microservices. It starts about having a real clear vision for what it is you’re trying to achieve. Then you can say, is microservices the right approach for this? Are we using microservices in the right way?”
Another well-known software architect, Chris Richardson, has identified several microservice anti-patterns that you should be aware of:
- Magic pixie dust: believing that microservices will cure all of your development woes
- Microservices as the goal: focusing on adopting microservices rather than improving the velocity and reliability of software delivery
- Scattershot adoption: numerous groups with an organization adopt microservices and implement supporting infrastructure without any coordination
- Trying to fly before you can walk: attempting to adopt microservices while lacking key development skills such as automated testing and the ability to write clean code
- Focusing on technology: focusing on cool (deployment) technology rather than the essence of the microservice architecture: service decomposition and definition
- The more the merrier: developing an excessively fine-grained architecture
- Red flag law: keeping in place policies and processes that obstruct rapid, frequent and reliably software delivery.
What are Microservices and Monolith Systems?
In this article, I’m considering the benefits and drawbacks of microservices compared to those of a monolithic system. Before beginning let’s understand what we mean by these terms.
The following definitions are from an article by Martin Fowler and James Lewis, “Microservices – a definition of this new architectural term.”
“In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.”
Independent service scalability
In most systems, the load and throughput characteristics are not uniform. Microservices give you the possibility to scale services independently as needed. When running a monolithic system, there is a structural limitation that requires the entire system to be scaled as a unit. This is an inefficient use of resources.
Independent development and deployment
Microservice architectures are well suited to continuous integration, continuous deployment, and delivery.
Small autonomous teams can develop, test, and deploy microservices independently and in parallel. With a monolithic system, more coordination across teams is needed. Releases of a monolithic system, therefore, tend to be larger in scope and done on a slower cadence. Also, as the scale of the deployment is larger, monoliths have a higher chance of creating issues compared to releasing a microservice.
The improved development and deployment features of microservices enable a faster time to market which can be a critical factor for many systems.
Changes are easier in a microservice architecture
Across a system, some services are more prone to change than others. A microservice architecture allows these services to be changed and deployed at their own pace.
When compared to a monolithic system, each service in a microservice architecture is easier to understand and change.
When making changes to a monolithic system:
- The team must have a deeper understanding of the entire system.
- It is more difficult to figure out which parts need updating.
- It is more difficult to understand the impacts across the system when code in one area requires changes. This increases the chance of introducing breaking changes.
Another factor in favor of microservices is service boundary stability. Service boundaries in a microservice are easier to keep consistent as they are explicit.
In a monolithic system, the in-process service boundaries are hard to enforce. Overtime these boundaries tend to break down leading to specific functionality being arbitrarily spread across the system. This makes maintenance more difficult and error-prone. As it can be difficult to understand how to correctly implement a change, you can end up in a downward spiral of declining code quality.
Microservices give you the ability to choose technology based on the specific requirements of each service. For instance, it might make sense to use a document database for one service, a graph database for another and a relational database for another.
It should be noted, there is a cost to having a more heterogeneous technology stack. As with any design decision, do not make this choice until you have carefully weighed the costs against the benefits.
Using a monolithic architecture requires a long-term commitment to a particular technology stack. Adopting a microservice architecture allows the internal architecture of each service to evolve independently. If required, a service may be swapped for a new implementation or even removed if it is no longer needed.
In a microservice architecture, if one service fails, and the failure does not cascade, other services may be able to continue to operate.
Note that stopping the failure from cascading does not happen intrinsically; the system must be designed to handle failure situations. This includes adopting design patterns such as:
- Circuit Breaker (See https://docs.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker)
- Bulkhead (See https://docs.microsoft.com/en-us/azure/architecture/patterns/bulkhead)
Microservices are a distributed system. Distributed systems are by their nature, unpredictable. With that comes complexity in developing, testing, operating and understanding the system. Examples of this complexity include:
- When a failure occurs, it can be difficult to find the root cause and understand how other services are affected.
- Transactions that span service boundaries add complexity to the design and operation of the system.
- End-to-end testing is more difficult to setup and execute compared to a monolithic system.
Monitoring and observability strategies need to be factored into the design right from the beginning. Having a robust monitoring strategy is a requirement for microservices.
Determining Service Boundaries is Difficult
Deciding on the service boundaries is one of the most important and difficult design decisions in a microservice architecture.
If your services are too fine-grained, you risk having:
- Excessive coupling between services. Higher coupling means that a change in one service can potentially affect multiple services, reducing the advantages of “Independent development and deployment” and “Changes are easier in a microservice architecture” described above.
- Performance issues due to network latency. If your services need to use too many other services to fulfill its use case, this can cause an excessive number of network calls, each of which adds latency overhead.
- Cyclical dependencies can cause a so-called distributed stack overflow. This is where a certain transaction is stuck in a loop of two services calling each other.
If you make your service boundaries too wide, that is having your services to coarse-grained, then you lose many of the benefits of microservices such as “Independent service scalability” and “Independent development and deployment.” These were described above in the Benefits section.
When deciding on service boundaries, you must also consider data consistency requirements. Transactional boundaries should be understood upfront. If transactions need to span multiple services, you need to consider how these transactions will be handled. Some options are compensating transactions, messaging with eventual consistency, the Saga Pattern, or redrawing your service boundaries.
For information on compensating transactions, see: https://docs.microsoft.com/en-us/azure/architecture/patterns/compensating-transaction
For information on Saga Pattern, see: https://microservices.io/patterns/data/saga.html
Also, Microsoft has a good article on data considerations for microservices. See: https://docs.microsoft.com/en-us/azure/architecture/microservices/design/data-considerations
One recognized strategy for determining service boundaries is to use Domain Driven Design (DDD). In DDD, the concept of the Bounded Context is useful for service boundary determination.
For more information on DDD Bounded Contexts, see the article “Identify domain-model boundaries for each microservice”: https://docs.microsoft.com/en-us/dotnet/architecture/microservices/architect-microservice-container-applications/identify-microservice-domain-model-boundaries
Even if you get your service boundaries correct, you may still face issues with network latency. This needs to be considered in your design. You should also plan for performance testing and latency monitoring on your production systems.
In addition to the technical challenges presented by microservices, organizational structure should be considered.
One of the main benefits of microservices is “Independent development and deployment” as noted in the Benefits section above. This is centered around small cross-functional teams (5 to 10 people) working independently. These teams should be organized around delivering business value, not around technical capabilities. If your organizational structure and culture do not support these types of teams, you will not realize the full benefits of a microservice architecture.
You should also consider Conway’s Law which states:
“Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.”
There is evidence, both anecdotal and empirical, that shows organizational structure influences the nature and quality of the systems they provide.
For more information, see the article “Microservices: Organizational Practices, Part 2” https://dzone.com/articles/microservices-organizational-practices-part-2
Do your teams have the required skills to develop and operate a microservice?
Microservices are a distributed system. You need to evaluate whether the teams have the skills and experience to be successful in building and operating a distributed system. If not, you may be better off with a modular monolith.
If you are not familiar with the modular monolith, I suggest you watch Simon Brown’s video presentation available on YouTube at https://www.youtube.com/watch?v=5OjqD-ow8GE
While microservices have many compelling benefits, before starting your journey to this architectural style, you need to understand the potential problems. As with any architectural decision, there are trade-offs.
If your microservices are not designed with enough care and attention, you may end up walking into one of the worst architectures there is, the distributed monolith. This is a system that has all the drawbacks of a distributed system with none of its benefits.
One potential path towards microservices is first building a well-structured modular monolith. If at a later point you are facing issues that need to be solved using a microservice architecture, you can break out one or two services from the modular monolith and slowly move towards microservices. This also gives you and your teams a chance to incrementally learn what it takes to build and operate a microservice.
As a final warning about the pitfalls that may lay ahead, I suggest you watch the following videos:
- Jimmy Bogard’s presentation “Avoiding Microservice Mega Disasters” https://www.youtube.com/watch?v=gfh-VCTwMw8&t=1802s
- Alexandra Noonan’s presentation “To Microservices and Back Again” https://www.infoq.com/presentations/microservices-monolith-antipatterns/?useSponsorshipSuggestions=true&itm_source=presentations_about_architecture-design&itm_medium=link&itm_campaign=architecture-design