Reading the Graph: Using Your Dependency Graph to Find the Hidden Seam

Before you extract a service, look at your dependency graph. The seams are already there — dense clusters, accidental bridges, and boundaries that can become clean internal interfaces or future services.

•15 min read
architecturedependency graphsmicroservicesrefactoringreading the graph

Most microservices extraction efforts start the same way. A team decides they want independent deployability, or better scalability, or cleaner ownership. They draw a boundary on a whiteboard — "the billing service goes here, the user service goes here" — and then spend the next six months discovering that the boundary they drew doesn't match the way the code is actually coupled.

This is the wrong order. The whiteboard should come after the dependency graph.

Your monolith's structure already contains information about where the natural boundaries are. Some clusters of modules are tightly coupled internally and loosely coupled externally. Some subgraphs communicate with the rest of the system through only one or two bridge modules. These are Hidden Seams — places where the codebase is already organized in a way that supports a cleaner boundary, even if nobody has formalized it yet.

The most useful thing about Hidden Seams is that you don't have to extract them to get value from finding them. Identifying a seam lets you enforce it as an internal module boundary, clarify ownership, and formalize the interface — all without paying the operational costs of a distributed service. When circumstances change and extraction becomes justified, the groundwork is already in place. But that's getting ahead of things. First: how to find them.

Finding Hidden Seams before you commit to a service boundary saves months regardless of what you decide to do with them.


What a Hidden Seam Looks Like

A Hidden Seam is a cluster of modules that satisfies two conditions:

  1. High internal cohesion: The modules within the cluster have many dependency edges between them. They're tightly integrated.
  2. Low external coupling: The cluster connects to the rest of the codebase through very few edges — ideally through a small number of clear entry points and exit points.

In a dependency graph, Hidden Seams look like dense subgraphs with thin connections to the rest of the graph. The thin connections are the seam itself — the interface through which the cluster communicates with the outside world. In a well-structured extraction, that seam becomes the service's API. In a modular monolith, that seam becomes an internal boundary that other modules are expected to respect.

The important point is that a seam is not just a cluster with few external edges. A useful seam has a bridge that can become an owned interface.

That distinction matters.

If you've read the Island pattern post, the structural description might sound familiar — both Islands and Hidden Seams can look like clusters with sparse external connections. Both can even have a single bridge node. The difference is what that bridge means.

An Island's bridge is accidental. External callers happened to converge there, but the bridge is not owned as an interface. The contract is implicit, undocumented, and fragile. A Hidden Seam's bridge is formalizable. It exposes a coherent set of operations, callers already use it like an API, and the boundary it represents is worth recognizing.

The graph topology can look nearly identical. What separates an Island from a Hidden Seam is whether the bridge is just accumulated coupling or the beginning of an intentional interface.

The distinction changes what you do next. An Island needs attention. A Hidden Seam needs to be recognized, strengthened, and potentially formalized.


Reading the Visual Pattern

A Hidden Seam usually emerges through three stages.

Three stages of boundary formation: No seam, Accidental bridge, and Hidden seam — showing how external coupling consolidates over time into a formalizable interface.

No seam

In the weakest case, external callers reach directly into many internal modules. There is a cluster, but there is no meaningful boundary. The rest of the codebase knows too much about the cluster's internals.

This is not extractable yet. Any attempted service boundary would have to replace many direct internal dependencies with remote calls, facade methods, duplicated logic, or awkward compatibility layers.

Accidental bridge

In the middle case, access has started to consolidate through one bridge module, but the bridge is still too broad, too leaky, or too unowned to be treated as a clean interface.

This is a useful signal. The codebase may already be telling you where a boundary wants to form. But the bridge still needs design work. It may expose implementation details. It may fan out to too many external callers. It may exist because of historical convenience rather than deliberate ownership.

An accidental bridge is not yet a service boundary. It is a seam candidate.

Hidden seam

In the strongest case, the cluster has dense internal relationships and a narrow, coherent bridge to the rest of the codebase. The bridge is small enough to reason about and clean enough to become a formal interface.

This is the pattern worth investigating. It may support service extraction later. It may also be valuable as an internal module boundary right now.

The ratio that matters is not the absolute number of external edges but the ratio of internal to external edges. A cluster with 50 internal edges and 5 external edges — a 10:1 ratio — is a much stronger seam candidate than a cluster with 20 internal edges and 15 external edges.

Higher seam ratio means more internal cohesion per external access point.


Why the Whiteboard Approach Fails

When teams define service boundaries on a whiteboard, they typically define them by domain concept — "billing", "authentication", "notifications", "inventory". These are intuitive boundaries that make sense as business concepts.

The problem is that domain concepts and code organization don't always align. "Billing" as a business concept might be implemented by code scattered across a dozen modules in different directories, each of which is tightly coupled to modules that are conceptually "not billing". Extracting billing as a service means either pulling all those modules across, or leaving some of them in the monolith and adding a network call where there used to be a function call.

The whiteboard approach discovers this misalignment after the service boundary has been committed to — after teams have been organized around it, after infrastructure has been provisioned for it, after contracts have been established. Changing the boundary at that point is expensive.

The dependency graph approach discovers it before any of those commitments are made.


How to Find Hidden Seams

Step 1: Visualize and Look for Low-Density Bridges

In your dependency graph, look for narrow connections between otherwise dense clusters. A cluster of 30 modules with many internal edges but only 3 edges connecting it to the rest of the graph is a Hidden Seam candidate. The 3 edges are the seam — the potential boundary.

The external edges can point in either direction. The rest of the codebase may call into the cluster, the cluster may call out to the rest of the codebase, or both may be true. What matters first is whether the boundary is narrow enough to reason about.

A cluster with many internal edges and few external edges is worth inspecting. A cluster with many internal edges and many external edges may still be important, but it is not yet a clean seam. It may need consolidation before it can become one.

Step 2: Identify the Bridge Modules

For each candidate seam, identify the modules at the bridge — the specific modules that connect the cluster to the rest of the codebase. These bridge modules define the seam's current interface.

Examine them carefully:

  • Are they clean interfaces, or are they implementation modules that happen to be imported externally?
  • Do they expose a coherent set of operations, or a grab-bag of functions that happened to be needed by callers outside the cluster?
  • How many callers do they have outside the cluster?
  • Do outside modules need to understand internal cluster concepts to use them?

A Hidden Seam with a clean bridge module is close to formalizable. A seam candidate with a messy bridge module requires interface design work before extraction is viable.

Step 3: Trace Shared State

Code coupling through imports is visible in the dependency graph. Data coupling through shared state is not. Before treating a Hidden Seam as extractable, trace all the ways the cluster shares state with the rest of the system:

  • Shared database tables: Does the cluster read and write to tables that are also used by modules outside the cluster? If yes, extraction requires either data duplication, shared database access, or a data ownership decision.
  • Shared in-memory state: Does the cluster depend on state that's managed by modules outside it? Caches, singletons, session objects?
  • Shared configuration: Does the cluster read from centralized configuration that the rest of the system also controls — for example, a monolith-wide feature flag store or a shared environment config object? If the cluster can't run without configuration it doesn't own, that's a coupling surface that needs to be resolved before extraction.

Data coupling that isn't visible in the import graph is one of the most common reasons whiteboard-defined service boundaries fail. Finding it before committing to a boundary is one of the most valuable outputs of the seam-finding process.

Step 4: Check the Call Direction

The bridge between a Hidden Seam and the rest of the codebase has a direction. Either:

  • The rest of the codebase calls into the cluster
  • The cluster calls into the rest of the codebase
  • Both sides call across the seam

The call direction tells you what kind of boundary is natural.

Call direction across a seam — three cases: rest of codebase calls in (natural API or facade), cluster calls out (natural worker or consumer), bidirectional coupling (not extraction-ready without resolving direction first).

A cluster that the rest of the codebase calls into may naturally become an API service or internal facade. A cluster that calls into the rest of the codebase may naturally become a worker, consumer, adapter, or orchestration component.

Bidirectional coupling across the seam is the most challenging case. It often indicates that the seam is not as clean as it appears. There may be a conceptual dependency in both directions that needs to be resolved through interface design before extraction is viable.


Validating a Seam Before Committing

Once you've identified a Hidden Seam candidate, validate it against three criteria before committing to the extraction.

Can the bridge become a clean API?

Sketch what the public interface would look like. Take the bridge modules' external-facing functions and ask: if these were REST endpoints, RPC methods, queue consumers, or internal facade methods, would the interface make sense?

Would the calling code need to know anything about the implementation inside the cluster?

If the interface is awkward, requires sharing implementation details, or exposes concepts that don't belong in a public contract, the seam needs interface design work before extraction is viable.

Can data ownership be cleanly assigned?

For each shared database table or data store, answer: who should own this data?

If every shared table can be cleanly assigned to one side of the seam — either inside the cluster or outside it — data coupling is manageable. If some tables are legitimately shared between both sides of the seam, you have a data ownership problem that will survive the extraction and become a distributed data consistency problem.

There's no purely technical solution to a data ownership problem. It requires a business decision about which service is the source of truth for which data. Make that decision before you extract, not after.

Does the seam align with team ownership?

Conway's Law is real. Services owned by a single team evolve coherently. Services owned by multiple teams — or worse, without a clear owner — accumulate the same Upward Arrows, Creeping Utilities, and Meshes inside the service that the monolith accumulates at large.

Before extracting a service, identify who will own it. If no team has a clear reason to own it — if ownership would be ambiguous or shared — that's a signal that the seam might be structurally valid but organizationally premature.

A service without an owner is an Island waiting to happen.


The Extraction Decision Framework

Hidden Seams that pass the validation criteria above are candidates for extraction. But not all valid seams should be extracted. Service extraction carries real costs: network latency, distributed tracing, deployment complexity, cross-service testing, on-call scope. These costs are worth paying only when the benefits are substantial.

Extract when:

  • The cluster has meaningfully different scaling requirements from the monolith
  • The cluster needs to be deployable independently
  • The cluster has a different release cadence or risk tolerance
  • The cluster would benefit from being implemented in a different language or runtime
  • The cluster's ownership is clear and distinct from the monolith's ownership
  • The seam is clean enough that extraction doesn't require significant interface design work

Don't extract when:

  • The motivation is primarily aesthetic
  • The cluster has the same scaling, deployment, and ownership profile as the monolith
  • The seam is clean structurally but the data coupling is unresolved
  • The bridge is accidental and has not been formalized into a real interface
  • The team doesn't have the infrastructure maturity to operate distributed services reliably

A Hidden Seam gives you evidence that extraction might be possible. It does not prove extraction is worth it.


Hidden Seams You Don't Extract Are Still Valuable

When a Hidden Seam doesn't meet the extraction criteria, identifying it still produces real architectural value. A documented seam is:

  • A bounded context with a clear owner, even inside the monolith
  • A module cluster with a defined interface that internal callers should use rather than reaching past the bridge
  • A candidate for future extraction when circumstances change — when the team grows, when scaling requirements diverge, when the infrastructure matures

Treating Hidden Seams as internal module boundaries produces many of the architectural benefits of microservices without the operational costs. The code is more cohesive, the interfaces are cleaner, and ownership is clearer.

This is the modular monolith approach: use the dependency graph to find and formalize the seams, enforce them as internal boundaries, and extract only when the economics genuinely justify it.

For many teams, this is the right destination, not a waypoint.


Reading the Graph to Inform the Decision

The dependency graph doesn't make the extraction decision for you. It gives you structural evidence that should inform the decision — evidence that is otherwise very hard to gather.

Without the graph, a team deciding whether to extract a billing service has to trace dependencies manually, interview developers who work in the area, read code they may not be familiar with, and make qualitative judgments about coupling. With the graph, they can see the cluster shape, count the bridge edges, identify the bridge modules, and make a quantified argument about whether the seam is extractable.

That's not a guarantee the extraction will succeed. Data coupling, organizational alignment, and infrastructure maturity matter as much as structural coupling.

But structural coupling is the one you can measure directly.

The graph gives you that measurement before you commit.


Conclusion

Service extraction fails most often not because the technology is wrong, but because the boundary was wrong. The boundary was drawn on a whiteboard before the structural evidence was examined.

The dependency graph contains that evidence. Hidden Seams exist in many monoliths — clusters of modules that are already behaving like bounded subsystems, already communicating through a limited bridge, already organized around a coherent concern.

Finding them is a matter of looking.

Whether you extract them, enforce them as internal boundaries, or simply document them as bounded contexts, you're working with the architecture rather than against it. The graph shows you where the structure already is. What you build on top of that is your decision.

Find natural service boundaries in your codebase at pvizgenerator.com →


Published in the Reading the Graph Series

The Star: When One Module Knows Too Much
High fan-in, high fan-out, and the module your entire codebase orbits around

The Chain: Deep Dependency Paths and the Teams That Can't Ship Fast
How long linear dependency chains create invisible coupling between unrelated parts of your system

The Mesh: When Everything Depends on Everything
What to do when your dependency graph stops having structure

The Hourglass: Your Most Dangerous Module Is Probably One You've Never Thought About
High fan-in above, high fan-out below — and a single module holding it all together

The Island: Isolated Clusters and the Ownership Vacuum They Create
Why self-contained subsystems are more dangerous than they appear

The Upward Arrow: When Infrastructure Knows About Business Logic
Abstraction inversion and how to fix it

The Creeping Utility: How Helper Modules Become Load-Bearing Walls
The lifecycle of utils.py and how to intervene before it's too late

Try PViz on your own codebase

Get dependency graphs, coupling signals, and a compressed bundle ready for your LLM — for any GitHub repository, in minutes.