Skip to main content

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-dtos library versioned in lockstep; any DTO change requires coordinated deploys of 4+ services.
  • A shared users table accessed by three services' ORMs directly.
  • Synchronous call chain frontend -> gateway -> orders -> payments -> fraud -> accounts where a timeout in accounts takes 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:

  1. Lockstep check. When we change one service's API, how many services typically need to deploy? If > 1 routinely, investigate shared libraries, shared DBs, or missing contract versioning.
  2. Shared-DB check. Are multiple services reading/writing the same database tables? That is a shared DB dressed as microservices. Covered in concept 07.
  3. Verb check. For each service, list its operations. If the list is GET, POST, PUT, DELETE only, it is an entity service. Find the missing verbs.
  4. 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).
  5. 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

  1. What is the single best test for distributed-monolith?
  2. Why is "one service per entity" almost always the wrong decomposition axis?
  3. 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 typeAppears in distributed monolith asFix
Connascence of Name (weakest)Field names must match across servicesTolerant readers (concept 08); semver contracts
Connascence of TypeDTOs shared as library require lockstep upgradesReplace shared DTOs with per-service models + translators
Connascence of MeaningHard-coded magic values (status=3) meaning the same thing across servicesPublish an enum in a schema; each service interprets; tolerate unknown
Connascence of PositionOrder of arguments / fields must matchUse 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

External canonical references

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:

  1. 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.
  2. Contracts next. Introduce explicit versioning, tolerant readers, CDC tests. Most lockstep deploys dissolve once contract discipline is in place.
  3. Decomposition rework. Merge entity services into capability services. Consolidate nano-services. Split any service that two teams currently share.
  4. 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.