Top 10 Microservice Anti-Patterns

Ten Common Anti-Patterns and How to Avoid Them

Lahiru Hewawasam
Bits and Pieces

--

Introduction

A well-built microservices architecture-based application can withstand the test of time by being scalable, flexible, and resilient to most issues that may come its way.

These architectures achieve a high level of resilience due to its loosely coupled components that can run independently therefore not being affected by other microservices within the application.

This being said there are always ways that a microservice architecture can fail and become ineffective.

In this article, we will look into some of the most common Anti-Patterns or design patterns that can bring out issues within a microservice-based architecture.

If you want a summary, here’s a list of the anti-patterns:

1. Monolith in Microservices

2. Chatty Microservices

3. Distributed Monolith

4. Over-Microservices

5. Violating Single Responsibility

6. Spaghetti Architecture

7. Distributed Data Inconsistency

8. Tight Coupling

9. Lack of Observability

10. Ignoring Human Costs

1. Monolith in Microservices

If you’re trying to build microservies while preserving a monolith architecture, you’ll run into issues with scalability, fault tolerance and many more. This is often caused by:

  1. Shared Databases: When you’re building microservices, it’s often a good idea to have a database per service. This lets you control the database type, schema rules, IOPS capacity on a per service rule thus letting you have finer control over scaling. But, if you use a single database for all your service, you make it harder for your app to grow.
  2. Complex Deployment: Despite being decomposed into smaller services, the deployment process is still complicated and time-consuming, requiring coordination between various teams and manual involvement. This limits the agility and flexibility achieved by implementing microservices.
  3. Inadequate Service Boundaries: Poorly specified service boundaries would result in overlapping functionality and unclear ownership. This can lead to duplication of work and difficulties in controlling and improving the architecture.

So, to avoid having monoliths in microservices, I’d recommend going ahead with a single database per microservices along with defining clear ownership of your microservices via a Domain Driven Design.

2. Chatty Microservices

Due to their decoupled nature, microservices often communicate with each other to process the workload of the application. While communication is key, over-communication or inefficient chatter between microservices will make them less efficient.

Let’s look at some scenarios that can significantly decrease efficiency while contributing to chatty microservices:

  1. Frequent Inter-Service Communication: Cases in which microservices within the system send a large number of requests to other microservices to accomplish minor tasks or obtain small amounts of data. This can generate large network traffic and increase response delay.
  2. Fine-Grained APIs: Microservices provide fine-grained APIs that require numerous calls to complete a single user request or business transaction. Each call may need serialization, network overhead, and even blocking I/O operations, increasing performance issues.
  3. Cascade of Calls: A single user request or transaction initiates a series of calls amongst multiple microservices, with each service relying on the others to complete its processing. This can cause a domino effect, in which the failure or delay of one service spreads to others, resulting in system-wide deterioration.

So, to avoid this issue, you’ll need to design your architecture in such a way that your services are decoupled and more scalable. You can introduce message queues, event busses or event topics by leveraging services like Amazon SQS, Amazon SNS and Amazon EventBridge.

As you can see, the Order Acknowledgment Microservice communicates with the Shipment, Inventory and Notification Microservices. However, it doesn’t directly communicate with these services. Instead, it uses the intediary services like SQS and SNS to decouple the communication behavior.

By doing so, you can avoid inter-service communication decouple the communicate between your services.

3. Distributed Monolith

This anti-pattern refers to an application that is designed and implemented as a distributed system but is composed of multiple interconnected components or services that are tightly coupled and therefore the microservices lack true independence.

Some of the key characteristics of this anti-pattern include:

  1. Lack of Service Autonomy: Each component in the distributed system lacks complete autonomy since it relies heavily on other components for their operation. This lack of independence makes it challenging to scale, deploy, and evolve various elements independently.
  2. Complex Interdependencies: The distributed system’s components have intricate interdependencies, with one relying on numerous others to function properly. This results in a web of relationships that is difficult to manage and understand, increasing complexity and risk.
  3. Shared State: Components of a distributed system share state or data directly, either via shared databases, caches, or direct communication. This shared state can cause data consistency challenges, race conditions, and scale difficulties.

For instance, consider this architecture:

You’re invoking three services — OrderService, PaymenrService and InvoiceService. These three services are made to function independently, but by chaining the invocation like this, you’re coupling the services to this one operation. By doing so, you introduce issues like linear scaling and create unncessary interdependencies.

Again, you can get rid of such issue by decoupling your microservices with services like SNS, SQS.

4. Over-Microservices

One of the most common misconceptions when designing microservice-based architectures is to break apart each function into a microservice; even the simplest of functions!

This introduces microservices where it is not needed and goes beyond what is necessary or beneficial to it being decremental to the overall performance of the application. It is crucial to break down microservices on what’s necessary while following Domain-Driven Design principles.

Key characteristics of the “Over-Microservices” anti-pattern include:

  1. Excessive Fragmentation: The system is divided into a significant number of microservices, which might result in dozens or even hundreds of services. Each microservice may only encapsulate a small portion of functionality, resulting in too fine-grained decomposition.
  2. Low Cohesion: Individual microservices lack coherence, which means they may not include logically connected or coherent sets of functionality. This can lead to scattered and fragmented business logic, making it difficult to comprehend and manage the system as a whole.
  3. High Coupling: Despite being partitioned into microservices, services may be tightly coupled due to considerable inter-service interactions and interdependence. Changes in one microservice may need changes in many other microservices, increasing complexity and risk.

One way to fix this problem is to adopt Domain Driven Design into your microservices and to create microservices for specific domains of your app. For a detailed guide on DDD, check this out:

5. Single Responsibility Violation

This is a fundamental violation of the responsibility of a function in an object-oriented design. It happens when a single function or microservice takes on multiple responsibilities or concerns that should ideally be separated. For example, when a payment processing microservice also handles user registration.

Let’s look at some scenarios that promote this anti-pattern:

  1. Lack of Design Principles Awareness: Developers may not be completely aware of or comprehend design concepts such as the Single Responsibility Principle (SRP), or they may fail to prioritize their application within the codebase.
  2. Inadequate Planning: Inadequate planning or analysis at the early phases of software design can result in an unclear delineation of duties for components, leading in the mixing of numerous concerns inside the same component.
  3. Misinterpretation of Requirements: Misunderstanding or misunderstanding of requirements can result in the introduction of unneeded or irrelevant functionality within a component, which violates the Single Responsibility Principle (SRP).

6. Spaghetti Architecture

Some antipatterns are self-explanatory just like the Spaghetti Architecture, where it refers to software architecture that lacks clear structure and organization, resulting in a tangled mess of interconnected components, modules or layers.

Key characteristics of the “Spaghetti Architecture” anti-pattern include:

  1. Lack of Separation of Concerns: The architecture fails to separate between different concerns or duties, leading to the mixing of business logic, presentation logic, data access logic, and other features inside the same component or module.
  2. Complex Control Flow: The architecture’s control flow is complicated and convoluted, with dependencies and interactions between components that are difficult to track or comprehend. This might result in unpredictable behavior and unintended consequences.
  3. High Coupling: Similar to what we looked at earlier, components or modules within the architecture are closely connected, which means they are extremely dependent on one another. Changes to one component sometimes need changes to several other components, causing a ripple effect across the system.

7. Distributed Data Inconsistency

This is when data is replicated across multiple nodes or services, and inconsistencies arise due to delays or failures in synchronizing updates across these replicas which leads to incorrect or outdated information being accessed by different parts of the system, resulting in incorrect behavior, data corruption, or integrity violations.

Key characteristics of the “Distributed Data Inconsistency” anti-pattern include:

  1. Asynchronous Updates: Data updates propagate asynchronously throughout the distributed system’s replicas or nodes. This can cause delays between executing a change and having it reflected on all copies of the data.
  2. Network Partitions: Network partitions or failures may occur due to unforeseen reasons, preventing updates from propagating to all replicas or resulting in discrepancies across replicas owing to incomplete updates.
  3. Conflicting Operations: Concurrent operations on the same data from many nodes might cause conflicts that are not adequately handled, resulting in inconsistent or damaged data.

To fix such issues, it’s important to leverage Microservice patterns such as the Saga Pattern to create and manage distributed transactions in your microservices.

8. Tight Coupling

Tight coupling may not be seen as an anti-pattern on its own but a key characteristic in many anti-patterns that we looked at previously. However, having microservices heavily dependent on each other or their outputs may cause issues within the system when scaling up.

This contributes to many anti-patterns such as but not limited to:

  1. Monolithic Architecture
  2. Spaghetti Architecture
  3. God Object
  4. Distributed Data Inconsistency
  5. Vendor Lock-in

9. Lack of Observability

This is a case where the application does not provide adequate insight into the internal state, operations, and performance. It makes it challenging for developers or administrators to observe the performance of the application or even to efficiently troubleshoot an issue.

Key characteristics of the “lack of observability” anti-pattern include:

  1. Limited Logging: The system lacks comprehensive logging mechanisms to capture significant events, errors, and actions that occur within it. This makes it harder to trace the execution flow and identify problems.
  2. Inadequate Metrics: The system does not provide useful metrics or telemetry data on its performance, resource use, and other crucial indicators. Without this information, it is difficult to evaluate the system’s health and identify possible bottlenecks or areas for improvement.
  3. Sparse Tracing: The system lacks distributed tracing capabilities for tracking the flow of requests and transactions across many services or components. This makes it harder to detect performance spikes, latency concerns, and breakdowns in distributed systems.

Consider using cloud native tools like AWS X-Ray or third party platforms like New Relic.

By doing so, you can get key insights into system errors and identify performance and scalability issues in a proactive manner.

10. Ignoring the Human Cost

This is when the primary goal is focused on meeting technical objectives and deadlines without adequately considering the impact on the well-being, morale, and work-life balance of the team or individuals involved in the project.

Key characteristics of the “ignoring the human cost” antipattern include:

  1. Overworking: Team members are frequently forced to work extended hours, including evenings, weekends, and holidays, to fulfill project deadlines or resolve unanticipated challenges. This can cause burnout, weariness, and decreased productivity.
  2. Unrealistic Expectations: Project timelines and deliverables are defined without regard for the team’s existing resources, skills, or capabilities. This creates unreasonable expectations and puts unnecessary pressure on team members to execute under tight deadlines.
  3. Micromanagement: Managers or team leaders use excessive control or micromanagement over team members’ work, which undermines autonomy, creativity, and drive.
  4. Lack of Support: When confronted with problems or difficulties at work, team members feel unsupported by management or colleagues. This can increase emotions of loneliness, tension, and disengagement.

Wrapping Up

We’ve looked at some of the most common anti-patterns that can be seen within organizations that are either just starting with their microservice journey or are evolving their practices from more traditional architectures.

These anti-patterns not only create bottlenecks in applications but also cause them to underperform and crash due to the various constraints that are introduced due to malpractices followed.

Therefore we must avoid such issues to ensure that our microservice architectures perform as intended by enabling scalability, flexibility and resilience.

I hope you have found this helpful.

Thank you for reading!

Learn More

--

--