Microservice Bad Smells and Anti Patterns
April 26, 2020
Microservice Architectures have been around for some years. Therefore, we gained more experience in building and maintaining such systems. This lead to the emergence of best practices and patterns in such systems.
However, with best practices there always come anti patterns and bad smells. In this article we will have a look at the most common problems in microservice systems.
In order to understand why certain practices are considered anti patterns we need to have a brief look at what we want to achieve with microservices by examining a definition of this architecture.
Definition of Microservices
Microservices are small, autonomous services related to a specific business capability, that work together. - Martin Fowler
I can’t emphasize enough the importance of the word autonomous in this definition. Most of the problems in we face in microservice architectures are related to violating the autonomy of single services.
Probably the most impactful violation of this aspect is created when we have wrong cuts in our system.
We speak of wrong cuts in microservices if the overall system is separated into strongly coupled, dependent microservices. Instead of creating a service, which acts autonomously on its specific business capability, we distribute one business capability among multiple services. The result is a large number of microservices that are dependent on each other and all we get in the end is a distributed monolith.
But how can we avoid this problem?
- This is one of several reasons we should start with a monolith first. Building a simple monolithic system in the beginning can help us to identify parts of the system that can act autonomously, which in contrast is difficult to get right from the start.
- Domain Driven Design (DDD) helps us to correctly identify boundaries of our system.
- Think messages first. The Tao of Microservices mentions an interesting concept: Base your system design on the messages sent within the system and not the objects in the domain. By focusing on communication between microservices we can minimize coupling between services.
Even if the code is correctly cut in our system, we can still create coupling by other means. One such problem is shared persistency.
We talk about shared persistency in microservices when two or more services access the same persistent data. The resulting problems include an inability to change the structure of data, because several services are dependent on the format and synchronization problems.
While some argue that each microservice must have its own database that no other microservice can access, others do not have such strict views on the matter. Especially in the early stages of a migration from a monolith to a microservice system it might be enough to give services ownership over specific tables.
But even if our services are decoupled in terms of functionality and persistency, we can create coupling via shared libraries.
Because we are trained to always keep the Do not Repeat Yourself (DRY) principle in mind, this is a very common problem leading to long discussions.
On one hand we do not want to reimplement the same or very similar things among many of our services, but on the other hand having shared libraries between services leads to problems. If services include common functionality via libraries we run into cases where we need to update the shared library. This is a problem because instead of performing a change locally in the microservice we need to adapt, we are changing a shared library. Which in turn also changes all other services that include this library.
Furthermore, shared libraries create technical coupling, because they are specific to certain programming languages (and often frameworks). Once we are dependent on too many shared libraries, we lose the ability to change a service from one language to another.
What can we do about this problem?
- The open-source test: We can test whether a library is too specific by viewing it from an open-source perspective. Is it general enough to be open-sourced? If the answer is yes, we have a library that might be used among services.
- Generate library code instead of writing it. A typical and common example for this are API Clients, wrapper libraries written to interact with APIs within the system. Instead of writing a shared library for this we should focus on creating an API specification from which we can generate our API clients for every language and service.
But coupling happens not only within the system, there’s also coupling between clients and services as a result of direct communication between clients and services.
Direct Communication between Clients and Services
Some of the key arguments for creating a microservice architecture are improved flexibility and easier maintanence of the overall system. To achieve this we need to be able to change parts within our application. Sometimes this might include a change in the API of a micrservice. Sometimes we need to change the protocol used between services.
When clients are talking directly to different microservices we lose a lot of these benefits. The address of a service can’t change anymore, making it difficult to relocate or split the service. Furthermore, a publicly exposed service always needs to provide a backwards compatible API.
We can avoid this problem by decoupling clients and services with an API Gateway.
Too many standards
Originally one of the promised benefits of microservices, having the possibility to use the best tool for the job can in practice result in problems.
When our application consists of services in too many programming languages and uses too many different libraries, frameworks and approaches, the system becomes unmaintainable. In Production-Ready Microservices Susan Fowler emphasizes this point. It is often beneficial to set certain standards and stick to them within an organization.
While this was just a subset of all problems in microservice systems out there, we looked at the most common microservice bad smells. As we saw the root of most problems lies in violating the autonomy in terms of functionality and evolution of a service. Therefore, if we keep an eye out for this problem, we will be able to avoid most of the common problems in our architecture.
A blog by Manuel Kruisz a Freelance Software Developer based in Vienna