Over-Engineering: When Microservices Are Too “Micro”
The risks of too many microservices
Microservices are known to provide certain benefits in terms of complex software systems. They allow for dividing such systems into independent modules that can be deployed, changed, and updated without affecting the entire system or one another. Microservices eliminate dependencies between development teams to certain extent and can improve the ability of a system to handle increased loads.
Components in a microservices-based architecture are loosely coupled. So, when one of them fails, it does not necessarily will cause the others to fail, too. Localizing and fixing a fault also becomes much easier. Moreover, microservices ensure a greater freedom of technological choices, making it possible to combine several technologies within one system.
However, it would be a mistake to believe that microservices are a universal solution to make things simple. Just as anything else, they might come with not only positive, but also negative effects. Their improper use may lead to over-engineering when an application is split into too many components and it gets hard to maintain and support the system.
So, at the Cloud Foundry Summit 2017, Alex Sologub of Altoros delivered an overall message of “being careful with microservices.”
“Engineers need to take the responsibility and design based on facts, not on their gut feelings.”
—Alex Sologub, Altoros
During his talk, Alex pointed out that having too many microservices may lead to:
- Slower development with more apps to build, deploy, and test
- Architecture becoming increasingly complex
- Significant overhead development and infrastructure costs
- An architecture that requires technologies not typical for a specific domain
- Loss of profit due to resources wasted on inefficient solutions
- Largely upfront design based mostly on assumptions how a system will evolve in the future
Alex emphasized that before creating another microservice, one needs to actually weigh the value it can bring against the expected risks and costs. Otherwise, you will just be wasting your time, resources, and money for something that gets you nowhere near optimization. Instead of innovation, you’ll be coping with a disaster.
How to avoid over-engineering?
On adopting microservices, it is recommended to follow the 12-factor app principles, clearly defining and isolating dependencies. This way, the system components could be replaced/updated independently (or almost independently).
In this endeavor, it is not the size of a microservice that matters, since the number of code lines or responsibilities are not representative. There are some other considerations to measure what’s enough and what’s too much about microservices.
In particular, according to Alex, ignoring empirical data can have significant adverse effects. The data actually describes the context of using a microservice, which helps to keep reasonable about engineering choices. So, prior to building microservices, pay attention to:
- User experience analytics. Make sure you know what pages users open and how frequently, how long they stay there, the way they use a software product, etc. For instance, if users stay on a page for less than two seconds, real-time updates only complicate things without making any real difference to a user. Or else, when a feature is never/rarely used, it is reasonable to skip implementing a separate microservice for it.
- Performance. Understanding how the load is distributed across the system components and how it changes in the course of time is also vital. This data is essential to outline the responsibilities for each of the microservices. It is essential to identify the bottlenecks for enabling availability and scalability, as well as resources required to build a microservice. (E.g., whether it needs a separate database, what kind of back-end support it requires, etc.)
- Use cases and their interrelation. Microservices are usually built around business capabilities and are part of a larger process designed to address specific business needs. Knowing precise use cases can also help to differentiate between those services that are to be incorporated into a permanent system and those that are only for temporal use. One of the ways to better assign responsibilities between microservices is a domain-driven design, addressing microservices-based architectures within the context they exist and evolve in.
- Existing source code. Prior to re-designing a monolith, one should find interdependent parts of the current code base and understand the nature of these dependencies. The history of changes in SVN may shed some light onto which elements should be updated in parallel (or one right after another). So, it is useful to mine your source code across all the project’s repositories and combine the parts of the code that change together frequently. Otherwise, if you update one of the microservices, you’ll have to update the other(-s), as well. To accelerate the process of detecting dependencies, you may try out some kind of data mining (based on IDs of pull requests/changes).
Having a comprehensive vision of how components interact and depend on each other, it is also easier to figure out their exact boundaries and methods of communication. When the decomposition is not clear, it shifts complexity from inside a component to the connections between components, which is a more difficult issue to handle.
Changing the processes
Once adopted, microservices will inevitably influence each stage of the development life cycle.
“Implementation of a software project can be described as a sequence of the following stages: design, development, testing, and deployment. Microservices affect work at each of the stages.”
—Alex Sologub, Altoros
Alex then went through the implications of using the microservices concept for each of the stages:
- Design. Make sure to clearly identify the responsibilities of each microservice you’re building.
- Development. Verify if you made the right choice when selecting technologies and decide on the iteration frequency.
- Testing. Define what microservices require testing and what are the best methods to do this.
- Deployment. Be clear on what services and how frequently you are going to deploy.
So, to reduce the risks of over-engineering, build a system with awareness what the system’s intended purpose is, how it functions, and what the relations between its components are, Alex says. Always keep in mind what you want to achieve at each development stage and verify your choices in terms of what you lose and what you gain through them.
Want details? Watch the video!
Related reading:
- Reducing Complexity of Software with Domain-Driven Design and Microservices
- Addressing Complex Software Systems with Microservices and CI/CD
- PaaS vs. IaaS for Microservices Architectures: Top 6 Differences