Microservices architecture has become popular over the past few years, primarily because of improved agility, faster time to market, and improved scalability. Microservices architecture, if used the correct way, can bring benefits to organizations in terms of agility and scalability.
Despite all the benefits that it provides, there are certain downsides to using the microservices architecture in your application as well. If you do not follow the recommended practices and guidelines, and if you do not have the right people, resources, tools, and technologies, your journey towards adoption of microservices architecture will never be a success.
This article presents a discussion on some of the common anti-patterns when working with microservices architecture and how to overcome them. Microservices – the antipatterns and their pitfalls
What are Microservices Anti-Patterns?
The difference between Design Patterns and Anti-Patterns is that, while the former relates to repeating solutions to issues often found in software programs, the latter implies recurring programming practices that cause problems rather than solve them.
Anti-patterns can be a major hindrance to implementing microservices in an enterprise successfully. Overengineering, incorrect application of design patterns, failure to adhere to recommended practices, and other factors contribute to the development of these problems.
Common Microservices Anti-Patterns
Some of the common anti-patterns include Break the Piggy Bank, Cohesion Chaos, Versioning Avoidance, Gateway for each service, Everything Micro, and so forth. In the sections that follow, I will walk you through the most common anti-patterns and pitfalls when working with microservices-based applications, and solutions for avoiding them.
This is one of the biggest concerns when you split an existing monolithic application into microservices. If a service needs all other microservices or libraries to be available to execute, that’s a distributed monolith. The choice between a microservices architecture and a distributed monolith depends on several factors. That’s the million-dollar question. To answer this question, we will need to be able to answer a few questions that follow.
First off, will a change in a microservice in your application require redeployment of other microservices of the application as well? If so, your microservices are not decoupled from each other. This breaks the basic purpose of adopting microservices architecture – a major benefit of microservices architecture is decoupling such that services can be independently developed, tested, deployed, and maintained. Finally, you have a closely connected architecture, which results in delivery entropy and cohesion chaos, as well as other problems.
The next question you should be able to answer is if your microservices are too chatty. Ideally, your microservices should follow the SOLID principles of software architecture and execute with no or minimal communication with other services. Another point you should consider is whether deployments are taking a longer time than usual because of dependency disorder.
Dependency disorder is an anti-pattern that occurs when you know that the services must be deployed in a particular order to ensure they work as desired. Such cohesion chaos can occur when there is no control on the functional separation of the concerns.
A good way to avoid this anti-pattern is by introducing an API Gateway. You should be able to define service boundaries, analyze all dependencies, evaluate your deployment process, and then implement strategies to reduce/eliminate such service interdependencies.
The next point to consider is whether your microservices have a shared datastore. In a typical monolithic architecture, the services share the same datastore. On the contrary, in a distributed microservice architecture each microservice should have its own datastore. When you have several microservices accessing a shared data store, resource contention becomes a major challenge.
Multiple services should not access databases in a microservice architecture due to the high collaboration required across teams. Instead, tables should be accessible by a single service, and services should communicate via application programming interfaces (API).
A shared datastore in a distributed microservices architecture can create deployment problems as well – if you change the database schema the services communicating with the database will be affected. Hence it is imperative that you maintain a datastore per service to simplify development, deployment and even data management.
This is an anti-pattern that is often encountered when refactoring an existing monolithic application to microservices architecture. When your monoliths grow bigger and bigger over time, deployments can take more and more time and maintenance can be a nightmare. The performance of the application might also degrade over time. You might think an application in such a state as a pig and it a good candidate for being refactored into microservices.
This problem can be solved by breaking down large chunks of source code into smaller, more manageable, independent, decoupled, isolated services. You should then be able to determine the service boundaries and then identify the specific services and then define them and the relevant entities properly.
When developing microservices-based systems, an API Gateway is required to provide a centralized location for authentication, throttling, orchestration, and other functions. At the same time, if you don’t have proper runtime governance you might end up building authentication, throttling, orchestration, and route etc. in each service. This would add complexity and there would not be any consistency in the implementation.
Without an API Gateway, you would usually build each of them in each service, making maintaining them for each service a daunting job. API Gateway may be used to consolidate, manage, and monitor an application’s non-functional needs, as well as coordinate cross-functional microservices to minimize roundtrips.
Consider a scenario in which all your services have full access to all of the database objects. That is another anti-pattern – commonly known as the Data Taffy or the Entangled Data anti-pattern. The problem here is that you would not be able to scale a specific module in your application and it is extremely difficult to pull the data apart. You would also face performance issues in this case and making structural changes to the database is also challenging since data is entangled.
The best solution to this issue is to isolate the data inside the application to distinct domains and ensure that only services relevant to a domain have access to the data. You can take advantage of access policies to control access to the database objects to a specific service.
Improper Versioning Strategy
When working on a microservices-based application, you might have many different independent services. Poor versioning strategy might lead to code that will be extremely difficult to manage over time. You should not bind the consumers of a service to a particular version of the service. Over time the complexity would increase since it would be difficult to manage the services and service upgrades.
Here is where versioning comes to the rescue. A simple way to use versioning is by specifying the API version as part of the route URL. You should design and architect your services assuming the change is inevitable – i.e., design for change. You should have a proper versioning strategy – you should be able to manage the service changes/upgrades over time and enable the consumers to upgrade gracefully.
Read: Versioning REST APIs.
Microservices Architecture Tutorials
The three most important business drivers for any software architecture include improved customer experience, enhanced agility, and reduced costs. The primary purpose of using microservices architecture is to enhance customer experience, lower the costs and be able to hit the market early. However, this purpose would be defeated if you aren’t aware of the anti-patterns related to implementing microservices architecture in an enterprise and how to tackle them.