Welcome back! In our last lesson, we established the "database-per-service" pattern as a cornerstone of microservice independence. We saw that while this grants services true autonomy, it forces us to abandon traditional database transactions and embrace eventual consistency using patterns like Sagas and the Outbox pattern.
This raises a crucial question: if data is distributed, how do we handle operations that need to read from multiple services? A naive approach can easily lead you down a path of building a system that is complex and fragile.
Today, we'll explore what not to do by identifying two of the most notorious microservice anti-patterns: the distributed monolith and chatty services. Recognizing these pitfalls is a critical skill for any architect, as it helps you avoid building a system that carries all the complexity of microservices with none of the benefits.
What is an Anti-Pattern?
In software engineering, a pattern is a reusable solution to a commonly occurring problem. An anti-pattern, conversely, is a common response to a problem that is ineffective and risks being counterproductive. It's a solution that seems like a good idea at first but ultimately creates more problems than it solves.
Let's begin by watching a short clip that introduces the "villain" of our story: the distributed monolith.
Don’t Build a Distributed Monolith: How to Avoid Doing Microservices Wrong - Jonathan J. Tower
This video, "Don’t Build a Distributed Monolith," by Jonathan Tower provides an excellent overview of the anti-patterns we'll be discussing.
Start with the introduction to the distributed monolith. The speaker sets the stage by explaining how teams often end up building this anti-pattern by mistake when they first adopt microservices.
Anti-Pattern #1: The Distributed Monolith
A distributed monolith is an application that is deployed as a set of microservices but remains as tightly coupled as a traditional monolithic application. It's often described as having the worst of both worlds:
- From Monoliths: You inherit the development friction, where a change in one service requires coordinated changes and deployments across many other services.
- From Microservices: You inherit the operational complexity of a distributed system, including network latency, partial failures, and complex deployment pipelines.
You end up with a system that is hard to develop, hard to deploy, and hard to operate.
How to Spot a Distributed Monolith
This anti-pattern isn't a single mistake but rather the result of several underlying issues, most of which stem from tight coupling.
Is Your Microservice a Distributed Monolith?
This article from Software Engineering Daily clearly defines the distributed monolith and its key symptoms.
Read the introduction and the first section, "Sign #1: Services are tightly coupled." Focus on understanding the different forms of coupling (behavioral, temporal, implementation).
As the article highlights, tight coupling is the core problem. Let's examine a few key symptoms that indicate you might be building a distributed monolith.
1. Shared Database
This is a classic red flag. If multiple services directly access the same database schema, you have created a massive point of coupling. A change to the database schema for one service's feature can break other services. This directly violates the database-per-service pattern we discussed in the last lesson.
2. Synchronous Communication Chains
This occurs when a single incoming request triggers a long chain of blocking, synchronous calls from one service to the next.
For example, Service A calls Service B, which waits, then calls Service C, which waits...

This synchronous chain creates severe temporal coupling: all services in the chain must be available and performant for the original request to succeed. A slowdown or failure in any single service can cause a cascading failure throughout the entire system.
Watch this segment to see a practical example of how this happens and why it's so detrimental.
Don’t Build a Distributed Monolith: How to Avoid Doing Microservices Wrong - Jonathan J. Tower
Let's return to the "Don't Build a Distributed Monolith" video.
Pay close attention to these two segments: Architectural Patterns: This section illustrates how shared databases and direct synchronous calls create a distributed monolith. Notice the diagram showing all services pointing to one database—a common mistake. Synchronous Communication: This part explains why synchronous communication is one of the worst offenders, leading to cascading failures and poor performance.
Anti-Pattern #2: Chatty Services
The "chatty services" anti-pattern is closely related to the distributed monolith and is a major contributor to it. It occurs when services require an excessive number of fine-grained remote calls to each other to complete a single business operation.
Imagine a client needs to display a user's order details. In a chatty architecture, this might happen:
- Client calls
API Gatewayfor order details. API GatewaycallsOrderServiceto get the order.OrderServicereturns an order with acustomer_id.API GatewaycallsUserServicewith thecustomer_idto get the customer's name.API GatewaycallsProductServicefor each product in the order to get product names.
Each arrow is a network call, adding latency and a potential point of failure.
Causes and Consequences of Chatty Services
This anti-pattern usually arises from poorly defined service boundaries.
1. Services are Too Small (Nanoservices)
In an effort to "do microservices right," teams can go too far and break down their domain into tiny "nanoservices." If a single business concept, like a user's profile, is split across UserProfileService, UserPreferencesService, and UserAddressService, you are virtually guaranteed to create a chatty system, as these services will need to constantly communicate.
2. Anemic Services / Entity Services
This is a very common trap for developers coming from a monolithic background. An "entity service" is a service that simply wraps a database table with CRUD (Create, Read, Update, Delete) APIs. For instance, an OrderService might only have GetOrder, CreateOrder, etc., without containing any real business logic.
The business logic then gets pushed out to the clients (or an API Gateway), which have to orchestrate calls to multiple entity services to perform any meaningful work, resulting in chattiness.
Let's watch a detailed explanation of this anti-pattern.
microXchg 2018 - Microservice Patterns & Antipatterns - Stefan Tilkov
Stefan Tilkov's talk, "Microservice Patterns & Antipatterns," offers a sharp analysis of this problem.
Watch these two sections back-to-back: The Entity Service: The speaker uses the 'Order Service' as a prime example of a service that tries to be a generic entity for too many contexts, leading to a bloated, coupled design. The Anemic Service: This describes services that are just thin wrappers around data, maximizing reusability at the cost of being useless on their own. Notice the "slippery slope" example of how over-generalization leads to services that are too fine-grained.
The primary consequences of chatty services are:
- High Latency: The total response time is the sum of all sequential network calls. This leads to a sluggish user experience.
- Reduced Availability: The overall success of an operation depends on every single call in the chain succeeding. If you have 5 calls, each with 99.9% availability, the total availability for the operation drops to .
- Tight Coupling: The client becomes tightly coupled to the internal data structure of multiple services.
A Broader Look at Anti-Patterns
The distributed monolith and chatty services are just two of many anti-patterns. They are often interconnected symptoms of deeper architectural flaws, primarily incorrect service boundaries.
Take a moment to look at the other anti-patterns in the diagram. You can see how issues like "Shared Libraries" or a "Frontend Monolith" also contribute to coupling, preventing services and teams from evolving independently.
Conclusion
In this lesson, we've explored what can go wrong when designing a microservices architecture. By understanding these anti-patterns, you are better equipped to steer your design in the right direction.
Key Takeaways:
- An anti-pattern is a commonly used but flawed solution to a problem.
- The distributed monolith is a system deployed as microservices but coupled like a monolith. It combines the operational complexity of distributed systems with the development friction of a monolith.
- Chatty services create excessive, fine-grained communication between services. This leads to high latency, low availability, and tight coupling.
- The root cause for both anti-patterns is often poorly defined service boundaries that don't align with business capabilities or that are decomposed too far.
Preview of the Next Module:
We've now covered the high-level architectural principles and major pitfalls of designing microservices. A recurring theme has been the need for loose coupling and asynchronous communication to avoid these anti-patterns. To build truly resilient and high-performance systems, we need to master the tools that enable this asynchronicity at the code level.
In the next module, we will take a deep dive into Async Python. We'll explore the fundamental concepts of async and await, understand how the event loop works, and learn to write non-blocking, I/O-bound code. This will provide the foundational programming skills you'll need to implement the robust, loosely-coupled services we've been designing.