Microservice Dependency Governance: Eliminate Circular Dependencies & Rebuild Boundaries

This article analyzes microservice architecture decay through a real e-commerce case study, detailing root causes of circular dependencies (direct and indirect), four practical elimination strategies (domain events, shared contexts, data snapshots, context merging), ongoing governance practices (visualization, review gates, dependency classification), and a six-step iterative boundary reconstruction process to maintain architectural quality during continuous business evolution.

Internet Backend Architecture Stack
Internet Backend Architecture Stack
Internet Backend Architecture Stack
Microservice Dependency Governance: Eliminate Circular Dependencies & Rebuild Boundaries

Introduction: Architecture Decay Begins After Initial Build

Many teams assume that implementing microservices, DDD, event-driven patterns, and eventual consistency completes the architectural work. In reality, production environments prove otherwise: architecture setup is only the beginning of pain. Continuous business iteration, changing requirements, new interfaces, temporary cross-service calls, and deadline-driven shortcuts accumulate. Initially clear bounded context boundaries gradually blur, service call relationships become entangled, and well-designed microservices slowly turn into a distributed monolith — the phenomenon known as architecture decay .

What Is Microservice Architecture Decay? A Real E-Commerce Case Study

In an e-commerce system, initial DDD-based decomposition yielded five services: User Service, Order Service, Product Service, Inventory Service, Coupon Service . Early boundaries were clean: Order called Inventory for deduction and Coupon for verification; Coupon only read basic user info; Product provided basic data queries.

As business iterated, ad-hoc requirements arrived:

Marketing campaigns required Coupon to read Order's historical records → direct RPC call to Order Service .

Product sales statistics needed → Product Service directly called Order Service for order data.

Quick user benefit display → User Service directly called Coupon, Order, and Product to aggregate data.

Urgent features → developers skipped domain events and hard-coded cross-service RPC calls .

After several iterations, symptoms emerged:

Changing Order internals indirectly affected Coupon, Product, and User.

Releasing Order required coordinating five or six services for regression testing.

Circular dependencies appeared : A called B, B called back A.

Architecture diagrams showed microservices, but underlying call graphs were indistinguishable from a monolith.

Key insight: Service boundaries are not static blueprints; they are dynamic products requiring continuous governance. Decomposition is only step one; dependency governance is the long-term architectural work.

Core Problems Microservice Dependency Governance Must Solve

1. Explicit Dependency Control

All cross-service calls must be observable and registered. Eliminate "stealth RPC additions." Many production incidents stem from developers adding a cross-service call without architecture review, leaving no trace afterward.

2. Identify and Eradicate Circular Dependencies

Circular dependencies come in two forms; many only spot direct cycles and miss the more dangerous indirect ones.

Direct circular dependency: A ↔ B (A calls B, B synchronously calls A).

Indirect circular dependency: A → B → C → A — longer chains, harder to detect, most common in production.

Risks of circular dependencies: Service startup deadlocks — services wait for each other during initialization. Synchronous call loops cause thread pool exhaustion. Transaction and rollback logic confusion leads to data inconsistency. Release order becomes undefinable; upgrades easily cause production errors.

3. Clean Up Invalid and Excessive Calls

Legacy interfaces remain deprecated but callers never remove them; entire large interfaces are called for a few fields; single business flows serially invoke seven or eight remote services, causing performance avalanches.

4. Iterative Boundary Reconstruction Under Business Evolution

As business grows, original bounded contexts no longer fit. Clinging to old architecture is not an option; a practical, incremental reconstruction process is needed — not a big-bang rewrite.

Root Cause Analysis: Where Do Circular Dependencies Come From?

The vast majority of circular dependencies are not technical bugs but business boundary design flaws ; they cannot be fixed by code changes alone.

Root Cause 1: Unreasonable Bounded Context Division

Two domains have overlapping responsibilities; the business itself is tightly coupled, so mutual calls arise naturally. Example: early split of "Order Fulfillment" and "Coupon Verification" into separate services, but their processes are highly entangled, leading to reverse calls.

Root Cause 2: Temporary Reverse Calls for Urgent Requirements

Most common scenario: Service A is the main flow; a new requirement needs Service B to fetch A's data. Developers avoid domain event refactoring and write a synchronous RPC to pull data backward — one shortcut plants a circular dependency time bomb.

Root Cause 3: Domain Object Model Misalignment

A business entity's ownership across contexts is undefined; both sides read/write the entity and call each other for data.

Root Cause 4: Lack of Dependency Visualization — Problems Discovered Too Late

No dependency dashboard means cycles go unnoticed during coding, surfacing only during load testing or production incidents.

Warning: Event-driven does not equal zero calls. Events only decouple synchronous strong dependencies; poorly designed events can still create indirect dependency cycles.

Four Practical Solutions for Circular Dependency Elimination

Production environments should pick and combine based on business context; no silver bullet exists.

Solution 1: Domain Event Decoupling (Preferred First Choice)

Convert synchronous bidirectional RPC into asynchronous event notification.

Case: After order completion, Coupon needs to update user usage statistics.

❌ Bad: Order calls Coupon; Coupon also synchronously calls Order in some scenarios → bidirectional dependency.

✅ Refactor: Order publishes "OrderCompleted" domain event; Coupon subscribes, consumes event, updates internal statistics.

Core idea: One side only publishes events, the other consumes — eliminates reverse synchronous call.

Applicable when: Business flow does not require real-time return values.

Solution 2: Extract Shared Context / Common Model

When two services mutually need each other's partial data that doesn't belong to either core domain, extract an independent shared domain.

Case: User and Order both need shipping address. Order calls User for address; User benefit calculations read Order's address.

✅ Fix: Extract address as independent shared context; both depend on shared service, no direct mutual calls.

Risk: Avoid overusing shared modules. Prevent shared context from bloating into a new big ball of mud. Only extract truly common, stable capabilities.

Solution 3: Data Redundancy + Local Snapshots to Eliminate Remote Queries

Service B frequently queries read-only data from Service A. Instead of RPC each time, sync via events and maintain a local snapshot in B.

Case: Coupon needs a few read-only Order fields. Listen to Order change events, cache snapshot locally, read from local DB.

Trade slight data consistency (accept eventual consistency) for removal of strong dependency. Suits read-heavy, write-light scenarios.

Solution 4: Merge Bounded Contexts (Don't Resist Merging)

Many teams obsess: finer splits are better. If two services are highly entangled and event refactoring cost is prohibitive, they probably shouldn't have been split. Merging into one bounded context is often the more rational choice.

Principle: Splitting is not the goal; reducing complexity is. Merge when appropriate; don't do microservices for microservices' sake.

Anti-pattern: Force-split two highly coupled services, write massive events and adapters to avoid cycles — overall complexity increases instead of decreasing.

Ongoing Dependency Governance: Catching Issues Early

Governance is not a one-off refactor; it must be embedded in the development workflow.

1. Dependency Visualization Dashboard

Collect RPC calls and message consumption relationships; generate service dependency topology graphs.

Before each release, inspect topology changes: any new unreasonable cross-service calls? Any new cycles?

Many internal architecture platforms provide this; lacking a platform, simple analysis from interface logs works.

Focus on: Indirect cycles — long-chain loops invisible to the naked eye, requiring tooling.

2. Architecture Review Gates

New cross-service calls must pass review:

✅ Is this call necessary?

✅ Can domain events replace synchronous call?

✅ Will it introduce new dependency cycles?

Ban developers from arbitrarily adding cross-service RPCs. Much decay originates from unreviewed temporary interfaces.

3. Dependency Blacklist & Weak Dependency Standards

Minimize strong synchronous calls.

Prefer event-synced snapshots for queries.

Avoid single business flows synchronously calling B, C, D, etc.

Distinguish strong vs. weak dependencies : core flows should minimize external strong dependencies to prevent one downstream failure from dragging down the main path.

4. Regular Technical Debt Reviews

Each iteration reserves capacity to sort legacy unreasonable calls, retire deprecated interfaces. Don't accumulate until total loss of control forces a rewrite.

Service Boundary Iterative Reconstruction: Complete Process Under Business Evolution

Business keeps growing; yesterday's reasonable bounded contexts become misfits in a year or two. Reconstruction must avoid big-bang rewrites — risk is extreme.

Here is a practical, smooth reconstruction procedure:

Step 1 — Assess Current State: Map current service call chains, data flows, domain entities; identify responsibilities that no longer belong to current contexts.

Step 2 — Define Target Boundaries: Based on current business reality, redefine bounded context responsibilities; produce target architecture diagram.

Step 3 — Isolation & Adapter Layer First: Don't touch business code yet. Add anti-corruption/adapter layers; run old and new logic in parallel.

Step 4 — Dual-Write & Gray Data Migration: For data ownership changes, use dual-write: write to both old and new stores simultaneously; run data comparison checks.

Step 5 — Gradual Traffic Cutover: Small-percentage canary; progressively shift traffic to new boundary logic; monitor alerts.

Step 6 — Decommission Old Logic: After full cutover runs stably for a period, retire old interfaces, clean up historical call chains.

Critical advice: In production reconstruction, reject "tear down and rewrite"; smooth evolution is the only viable choice.

High-Frequency Pitfalls Summary

❌ Don't treat domain events as a cure-all; events still create dependencies — just async instead of sync — and still need governance.

❌ Circular dependencies are rarely pure code problems; most root cause is domain boundary design defects .

❌ Microservices are not better when split finer; governance cost of unreasonable splits far outweighs benefits — merge when needed .

❌ Dependency governance cannot rely solely on architects' post-mortems; must embed into daily development review processes .

❌ Reconstruction must not seek one-shot completion; accept phased iterations, prioritize production stability .

❌ Beware of numerous "temporary interfaces"; temporary interfaces often live forever and are the biggest driver of architecture decay .

Conclusion

DDD domain modeling, event storming for service decomposition, four-layer code architecture, event-driven eventual consistency — these solve the 0-to-1 architecture build .

Dependency governance, circular dependency eradication, continuous boundary iteration — these solve the 1-to-N architecture quality preservation .

Most companies' microservices fail not because initial architecture design was poor, but because they lack continuous governance mechanisms. Business iteration lets architecture rot bit by bit until maintenance costs become uncontrollable.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

microservicesDomain-Driven DesignrefactoringBounded Contextservice-boundariescircular-dependenciesdependency-governancearchitecture-decay
Internet Backend Architecture Stack
Written by

Internet Backend Architecture Stack

Over ten years of hands-on Java backend architecture experience, focusing on sharing mainstream internet technologies such as Java backend, microservices, distributed systems, high concurrency, performance optimization, and source code analysis. Building a platform for Java technologists to exchange and grow, collaborating with peers to advance together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.