Avoiding Distributed-Monolith and Entity-Service Anti-Patterns
What This Concept Is
Two common failures that look like microservices but behave like a worse version of a monolith.
- Distributed monolith. A set of services that cannot be deployed independently. Changes propagate across services in lockstep, often because they share a database, share DTOs through a single "common" library, or chain synchronous calls so deeply that any one service's downtime cascades to all.
- Entity service. A service whose scope is a single noun ("Customer Service", "Product Service", "Order Service") with only CRUD operations. Business behavior is not owned by the service -- callers orchestrate it over the network. Every meaningful workflow becomes a chain of entity-service calls.
Both have the same symptom: you pay the costs of distribution and receive almost none of the benefits.
Why It Matters Here
Most failed microservices migrations you will witness will be one of these two patterns. The failure is diagnosable and avoidable once you recognize the markers.
Concrete Example
Distributed monolith markers, real-world:
- A shared
common-dtoslibrary versioned in lockstep; any DTO change requires coordinated deploys of 4+ services. - A shared
userstable accessed by three services' ORMs directly. - Synchronous call chain
frontend -> gateway -> orders -> payments -> fraud -> accountswhere a timeout inaccountstakes down checkout end-to-end. - Release train: "we deploy everything on Wednesday." That is one deploy, with more steps.
Entity service, concrete: Imagine a "Customer Service" with endpoints GET /customers/:id, POST /customers, PATCH /customers/:id. The "update customer email" business logic -- validate uniqueness, send verification email, update auth system, emit CustomerEmailChanged event -- lives in the caller (say, a BFF). So does the logic for "promote to VIP", "deactivate account", and so on. The Customer Service is a network-mounted database.
Contrast: capability-scoped services.
- Accounts Service -- owns customer identity. Exposes
createAccount,requestEmailChange,confirmEmailChange,deactivateAccount. Each verb contains its business rules. Emits events on significant state changes. - Loyalty Service -- owns tier logic. Exposes
grantPoints,promoteTier,downgradeTier. Consumes events from Accounts to reason about status.
The second design is decomposed by capability, not by entity. It owns behavior, not just rows.
Common Confusion / Misconception
"We are not a distributed monolith because each service has its own repo." Independent repos are necessary but nowhere near sufficient. The real test is deployability. If any service change typically requires another service's deploy to land, it is a distributed monolith regardless of repo layout.
"Entity services give us reuse." The reuse is illusory. Every caller reinvents the surrounding business logic, and the logic drifts between callers. You end up with two implementations of "what can update a customer", one in each BFF.
How To Use It
Run this checklist on any proposed or existing decomposition:
- Lockstep check. When we change one service's API, how many services typically need to deploy? If
> 1routinely, investigate shared libraries, shared DBs, or missing contract versioning. - Shared-DB check. Are multiple services reading/writing the same database tables? That is a shared DB dressed as microservices. Covered in concept 07.
- Verb check. For each service, list its operations. If the list is
GET,POST,PUT,DELETEonly, it is an entity service. Find the missing verbs. - Call-chain depth check. Draw the synchronous call chain for a core workflow. If it is more than 3 hops deep, you have coupling that will surface as cascading failure (cluster 4).
- Orchestration location check. Where does the "checkout" business logic live? If it lives in the caller (BFF, frontend, gateway), the services are probably entity services.
Check Yourself
- What is the single best test for distributed-monolith?
- Why is "one service per entity" almost always the wrong decomposition axis?
- Why does entity-service design tend to create two different implementations of the same business rule?
Mini Drill or Application
Take a known decomposition (from concept 04 or 05, or a real system). In 10 minutes:
- Score it against all five checks above.
- Rewrite any entity service into a capability-scoped service by naming the verbs it should own.
- Propose one change that would break a lockstep dependency.
How This Sits In The Module
Cluster 3 (data ownership + contracts) prevents distributed-monolith at the data layer. Cluster 4 (resilience) stops deep call chains from becoming cascading failures. Cluster 5 (deployment independence) measures whether you have actually escaped the anti-patterns.
Connascence as the Coupling Vocabulary
The precise way to describe what goes wrong in distributed monoliths and entity services is connascence (Meilir Page-Jones, revived in FoSA chapter 4). Two components are connascent if a change in one requires a change in the other. Stronger forms of connascence across a service boundary are the smoking gun:
| Connascence type | Appears in distributed monolith as | Fix |
|---|---|---|
| Connascence of Name (weakest) | Field names must match across services | Tolerant readers (concept 08); semver contracts |
| Connascence of Type | DTOs shared as library require lockstep upgrades | Replace shared DTOs with per-service models + translators |
| Connascence of Meaning | Hard-coded magic values (status=3) meaning the same thing across services | Publish an enum in a schema; each service interprets; tolerate unknown |
| Connascence of Position | Order of arguments / fields must match | Use named parameters / schema validation |
| Connascence of Algorithm (strongest across process boundary) | Services must implement the same calculation (e.g., tax logic) | Extract a service that owns that logic, or publish a shared library with the discipline that only the owner changes it |
If you can put a name on the coupling, you can target it with a specific fix. Teams that say "we have coupling problems" and stop there never make progress. See FoSA: Connascence.
Read This Only If Stuck
Local chunks
- FoSA: Modularity -- for the counter-point: how to stay a well-modularized monolith if you cannot escape these traps.
- FoSA: Measuring Modularity and FoSA: Measuring Modularity 2 -- afferent/efferent coupling numbers you can actually measure.
- FoSA: Connascence -- the coupling vocabulary used above.
- FoSA: Reuse and Coupling -- the trade-off between reuse and coupling; relevant for shared-library anti-pattern.
- FoSA: Fitness Functions -- how to enforce "no cross-service DB access" automatically.
- Primer: Application Layer -- Microservices (disadvantages) -- the "disadvantages" list is effectively a warning about these anti-patterns.
External canonical references
- Chris Richardson, Anti-pattern: the distributed monolith -- extended examples.
- Michael Nygard, "Design Microservice Architectures the Right Way" (QCon/GOTO talks) -- anti-patterns covered in depth.
- Sam Newman, When to use (and not use) microservices (blog) -- explicit anti-patterns in the "don't do this" section.
- Martin Fowler, MicroservicesAndMonolithsFirst -- counter to the "distributed monolith by default" failure.
- InfoQ, Distributed monolith to microservices: Lessons learned -- case study.
- ThoughtWorks Radar, Build your own service mesh -- the "god gateway" anti-pattern discussed in the context of shared infrastructure.
- Chris Richardson, Microservice chassis -- the positive pattern that replaces the shared-DTO-library anti-pattern.
Depth Path
- Sam Newman, Building Microservices (2nd ed.) chapters on "Pitfalls" and "Database-level coupling" contain more specific examples. Return after concept 07.
- Meilir Page-Jones, What Every Programmer Should Know About Object-Oriented Design -- the original connascence text. Dense; the FoSA chapter is a sufficient summary for most readers.
Other Common Anti-Patterns Worth Naming
These are less common than the two primary anti-patterns but just as corrosive.
- Nano-services / excessive fine-graining. Services so small they represent single functions ("Send Email Service", "Format Date Service"). Operational overhead per service dominates the value; consolidation is cheap.
- "God" gateway. Gateway (or BFF) grows business logic -- validations, orchestration, custom domain rules -- instead of cross-cutting concerns. Becomes a new bottleneck; services become anaemic.
- Shared library with too much logic. Libraries that contain domain logic force coordinated releases: upgrading the library means redeploying every consumer. Keep shared libraries to thin cross-cutting concerns (logging, config, tracing SDK).
- Chatty cross-service communication. The decomposition forces many small calls to render one screen. Usually a sign that the service boundaries are too fine or that a BFF (concept 11) is missing.
- "Microservice" that is actually a batch job. A service that only runs on a schedule, does not expose an API, and owns its own database. Might be fine -- but confirm it has a clear owner and failure mode; often these drift into orphan status.
Tell-Tale Slack-Channel Symptoms
Beyond technical checks, you can often diagnose anti-patterns by the organizational traffic:
- "Release train is Wednesday" -- usually distributed monolith.
- "Who owns
common-dtos?" -- usually shared-library anti-pattern. - "I need the X team to ship their change first" -- lockstep coupling.
- "We had to rebuild the query in three services" -- entity services with shared data model.
- "We need to roll back, but that requires three other services to also roll back" -- lockstep or shared DB.
If these sentences are common in your team's chat, you have the condition regardless of what the architecture diagram says.
Recovery Playbook
If you diagnose these anti-patterns, the recovery is usually:
- Data first. Break shared database coupling before anything else (cluster 3). Even if some coupling remains, owning the data is the precondition for owning the service.
- Contracts next. Introduce explicit versioning, tolerant readers, CDC tests. Most lockstep deploys dissolve once contract discipline is in place.
- Decomposition rework. Merge entity services into capability services. Consolidate nano-services. Split any service that two teams currently share.
- Topology alignment. Adjust team ownership to match the new service boundaries. Without this step, drift will re-create the anti-patterns.
Expect this to take quarters, not weeks. Each step is a migration, and each benefits from the strangler-fig discipline from concept 03.