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.
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.
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.
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.
Whenever a company is looking to migrate to microservices, they need to act with two main goals in mind:
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.
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.
Author Mark Richards shares;
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.
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.
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.
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.
There are dozens of anti-patterns and pitfalls you should seek to avoid in your migration to microservices architecture.
We take a proactive approach to make the most of microservices. Contact us today to learn more.