Microservices architecture has achieved nearly the star status thanks to its ability to transform disconnected, dependent systems into individual, fully functional, self-sufficient applications that offer real value for a business.
Of course, while adopting new approaches like microservices is exciting, it requires careful consideration to avoid costly errors.
What Are Anti-Patterns?
Some time ago, service-oriented architecture, or SOA, was the talk of the town, much like microservices are today.
The result was that countless companies took a blind leap before fully characterizing the pros and cons, and they ended up with costly challenges. If you're not careful, you may see history repeating itself with microservices architecture.
For starters, let's define the term. Anti-patterns are just what they sound like.
They go against the established microservices patterns, and they sound like a good idea at first, but you quickly wind up in trouble.
Understanding Adoption Anti-Patterns
Not only will you find anti-patterns throughout the implementation and day-to-day use of microservices, but you can also see them in the pre-adoption phase when a company is deciding whether or not it's the right architecture for their use case.
Here's a brief overview of some of the most common adoption anti-patterns that can help you stop a doomed, misguided adoption of microservices in its tracks.
Pixie Dust Anti-Pattern: If you think a sprinkle of microservices can solve a company's development problems, you're wrong. This is the most common anti-pattern given that microservices and service-oriented architecture (SOA) are both exciting and alluring concepts these days, but that in itself is not reason to adopt them.
Short-Sighted Anti-Pattern: If adopting microservices is the "goal" of the implementation process, and success is measured by how many services are created in the adoption process, this is an anti-pattern that dooms a team to failure due to their lack of long-term planning and understanding of business impact.
Blinded By Tech Anti-Pattern: Just as making the adoption the goal, focusing too much on the technology behind microservices, like the deployment infrastructure, leads to neglect of key issues, like service decomposition. Both anti-patterns lack a big picture view, which is crucial to success.
Scattershot Anti-Pattern: When multiple teams try to adopt microservices without coordinating the adoption efforts with one another, this leads to the scattershot anti-pattern. All teams may have good reason for adopting microservices, but they won't be realized unless they align their plan and goals.
Dirty Adoption Anti-Pattern: Many companies who want to adopt microservices are trying to fly before they can walk, meaning they don't yet have their basic software development techniques in order. Your teams should be producing clean code with good designs and making use of solutions like automated testing before moving to microservices.
Fine-Grained Anti-Pattern: When teams take the "more the merrier" approach to microservices, it leads to the intentional creation of an extremely fine-grained architecture, which creates unnecessary complexity and separation.
With these adoption patterns aside, let's explore some of the most common anti-patterns that effect successful implementation and on-going use of the microservices architecture.
To divide monolithic applications into single-purpose services.
To migrate monolithic data into separate, small databases for each service.
In an attempt to apply these two goals, many companies end up with a data-driven migration anti-pattern that results in far too many migrations.
Since data migrations are inherently complex and prone to error, even more so than a source code migration, you'll ideally only migrate the data for each microservice one time.
By understanding the importance of data above functionality, you can avoid this microservice anti-pattern.
In other words, you can avoid this anti-pattern by migrating the service's functionality first and then adding boundaries to the context of the data and service later.
The Timeout Anti-Pattern
When a service consumer can't communicate with a service—meaning it's unavailable—the service consumer usually receives notification within milliseconds.
Then the service consumer can choose to retry several times or pass the error onto the client right away.
However, in the event that the service is reached and a request is made, you must address the question of what happens if the service fails to respond.
Often, the obvious answer is to use a timeout value, or else the service consumer can wait indefinitely for a response.
Anyhow, this can lead you to the timeout anti-pattern.
Calculating a logical timeout value seems straightforward enough—you don't want to re-submit a request right away as that can cause a duplicate, but you also don't want to cancel a request that may simply be waiting for confirmation on its success.
As a solution, many will choose to calculate the service's database timeout and use that as a basis for the timeout value.
So, if a service generally takes two seconds but under load can take five seconds, you'd double it to get a 10-second timeout value.
While this seems like a perfectly logical solution to the timeout problem, it causes every request from service consumers to have to wait 10 seconds just to find out the service is not responsive... In most cases users won’t wait more than 2 to 3 seconds before hitting the submit button again or giving up and closing the screen."
The circuit breaker pattern is the better option if you're considering a problem that would logically lead you to the timeout anti-pattern.
Reach-In Reporting Anti-Pattern
Data for microservices is typically moved to separate databases, which is great for services but not so great for reporting.
Reporting in a microservices environment is generally handled in one of four ways: database pulling, HTTP pulling, batch pulling, or event-based pushing.
The former three pull data from service databases, which is where the name for this anti-pattern comes in.
The "reach-in reporting" anti-pattern starts out logically like all other anti-patterns, with this one working on the obvious declaration that the quickest and simplest way to get fresh data is to access it directly.
However, this will ultimately lead to interdependencies between both services and the reporting service, directly contradicting the main goals of microservices.
As soon as you couple applications through a shared database, even with the sole intent of powering reports, the services no longer hold their own data.
So, you must rethink your reporting methods to align with the guidelines of microservices and the goal of maintaining the ever-important bounded context between a service and its data.
Service-Level Gateway Building Anti-Pattern
Many companies pursue microservices without an API gateway or runtime governance, which will only lead to added complexity and blurred transparency.
For example, it's common for developers to begin implementing functions and features like throttling, orchestration, transformation, routing, and authentication at the service-level, but this begins to create inconsistencies.
Aside from adding unnecessary complexity, the lack of runtime governance means teams lose sight of who has implemented what where.
What's more, making adjustments and additions to each service means they'll ultimately meet the requirements of some projects, but not others.
With a gateway in place, filtering and enrichment patterns can enable a team to avoid this common anti-pattern that begins with good intentions of adding functionality, but ends with a mess of configurations that burden systems and management.
Invest in an advanced API management solution to help centralize monitoring of various functions and configurations, and opt for a gateway to orchestrate cross-functional microservices, too.
Technical Layer Separation Anti-Pattern
With service-oriented architecture (SOA), a simple misunderstanding of how to best achieve re-usability across services can lead to a handful of costly anti-patterns, but one of the worst examples here is summed up in the Technical Layer Separation Anti-Pattern.
In this scenario, developers again start with good intentions, this time putting too much focus on the technical cohesion rather than reusable functionality.
A common example is where teams put several services together to form a data access layer in order to expose tables, which sounds highly reusable, until you realize this creates a physical layer that requires management by a horizontal team, resulting in a service with delivery dependency.
Again, dependencies works directly against the core of microservices, so this anti-pattern doesn't work because services end up lacking autonomy.
In addition to adding dependencies to service delivery, this anti-pattern also created runtime inefficiency.
Teams who follow this anti-pattern will ultimately end up with orchestration services, data services, and business services, each one serving a technical concern, but causing the formation of teams to manage each layer, creating sprawl with no one single owner of a given capability.
To avoid this anti-pattern, focus on keeping each service as an autonomous business entity.
Logical separation is fine, but services must remain entirely self-contained and scalable.
Rewriting some code across services is an acceptable trade-off, but services must be separated by business capability, and never technical concern.