Module 3: Understand an Existing Architecture
Dependency Maps and Dependency Graphs
Dependency Maps and Dependency Graphs
Capsule description
You're going to learn to generate dependency maps — visual and textual representations of which module depends on which in a codebase. This is the first artifact of your architecture map and the most fundamental: without knowing the dependencies, you can't trace flows, you can't identify patterns, and you can't predict the impact of changes.
A dependency map answers the most important question before any refactoring: "If I change this module, what other modules are affected?" The answer to that question determines the risk, the effort, and the priority of any change. Without a dependency map, you estimate by eye. With a dependency map, you decide with data.
In this capsule you'll learn to use Claude Code to generate dependency maps at three zoom levels — from the bird's-eye view of 3-5 main components to the detail of individual functions. You'll learn to read and interpret those maps, detect problematic patterns like circular dependencies and high fan-out, and produce artifacts another developer can use to understand the system's structure.
What a Dependency Map Is
The fundamental idea
A dependency map is a graph where:
- Nodes = modules, files, classes, or functions
- Edges = dependency relationships (imports, calls, inheritance)
If user_service.py imports user_repository.py, there's an edge from user_service to user_repository. That means user_service depends on user_repository — if you change user_repository, user_service could break.
What a dependency map shows
What you SEE: What it MEANS:
user_service -> user_repository "user_service uses user_repository"
"If you change user_repository,
review user_service"
auth_middleware -> user_service "auth needs user_service"
"You can't remove user_service
without breaking auth"
payment_service -> user_service "Payments queries users"
payment_service -> email_service "Payments sends emails"
"payment_service has a fan-out of 2"
Why it matters
Without a dependency map:
Developer: "I'm going to refactor user_repository"
Result: 💥 auth, payments, notifications, and admin break
Developer: "I didn't know 4 modules depended on this"
With a dependency map:
Developer: "I'm going to refactor user_repository"
Map: user_repository is imported by: user_service,
auth_service, payment_service, admin_service
Developer: "OK, I need to update 4 modules. I'll start
with the one that has the fewest dependencies of its own."
Three Zoom Levels
The most common mistake when creating dependency maps is trying to show everything at once. A map with 50 modules and 200 arrows isn't a map — it's noise. The solution is to work with three zoom levels.
Level 1: High-Level (3-5 main components)
This level answers: "What are the big blocks of the system and how do they connect?"
You to Claude Code:
> "Analyze the structure of this project. Identify the 3-5
> main components (not individual modules, but groups
> of modules that fulfill a function). Show the dependencies
> between those components as a Mermaid diagram."
Claude Code responds:
graph TD
API["API Layer<br/>routes/, middleware/"]
SERVICES["Business Logic<br/>services/"]
DATA["Data Access<br/>repositories/, models/"]
EXTERNAL["External Services<br/>clients/"]
CONFIG["Configuration<br/>config/, utils/"]
API --> SERVICES
API --> CONFIG
SERVICES --> DATA
SERVICES --> EXTERNAL
SERVICES --> CONFIG
DATA --> CONFIG
EXTERNAL --> CONFIG
How to read this diagram:
- The arrows go from "depends on" to "is used by"
- API depends on Services and Config
- Services depends on Data, External, and Config
- Config doesn't depend on anyone — it's the base
When to use Level 1:
- To explain the system to someone new
- To understand the big picture before digging deeper
- To verify that the architecture has clear layers
Level 2: Medium (modules within each component)
This level answers: "What modules are inside each component and how do they connect?"
You to Claude Code:
> "Now dig into the 'Services' component. List all
> the modules in services/ and show the dependencies between
> them. Also include which modules from other components
> import them. Format: Mermaid diagram."
Claude Code responds:
graph TD
subgraph Services
US["user_service.py"]
PS["payment_service.py"]
NS["notification_service.py"]
AS["auth_service.py"]
OS["order_service.py"]
end
subgraph Repositories
UR["user_repository.py"]
PR["payment_repository.py"]
OR["order_repository.py"]
end
subgraph External
EC["email_client.py"]
SC["stripe_client.py"]
end
US --> UR
PS --> PR
PS --> US
PS --> SC
NS --> EC
NS --> US
AS --> US
AS --> UR
OS --> OR
OS --> US
OS --> PS
How to read this diagram:
payment_servicedepends onuser_service,payment_repository, andstripe_client— it has a fan-out of 3user_serviceis imported bypayment_service,notification_service,auth_service, andorder_service— it has a fan-in of 4user_serviceis the hub: the most connected module in the system
When to use Level 2:
- To plan a specific refactoring
- To understand the impact of changing a module
- To identify modules with too many dependencies
Level 3: Low (functions within a module)
This level answers: "What functions does this module have and which are called from outside?"
You to Claude Code:
> "Analyze user_service.py. List all the public and
> private functions. For each public function, show which other
> modules call it. Format: table."
Claude Code responds:
| Function | Type | Called from | Description |
|---|---|---|---|
get_user(user_id) | Public | auth_service, order_service, admin_routes | Gets a user by ID |
create_user(data) | Public | auth_service, user_routes | Creates a new user |
update_user(user_id, data) | Public | user_routes, admin_routes | Updates user data |
delete_user(user_id) | Public | admin_routes | Deletes a user (soft delete) |
get_user_with_orders(user_id) | Public | order_service, admin_routes | User with their orders |
_validate_email(email) | Private | (internal) | Validates the email format |
_hash_password(password) | Private | (internal) | Hash with bcrypt |
_check_duplicates(email) | Private | (internal) | Verifies unique email |
When to use Level 3:
- To understand exactly which functions are the "public API" of a module
- To decide whether to split a large module
- To find functions that are private but should be public (or vice versa)
Generating Dependency Maps with Claude Code
Basic prompt: dependency overview
You to Claude Code:
> "Analyze this project's dependencies. For each Python
> file in src/, list which other project files it imports.
> Don't include external dependencies (pip packages), only internal
> project imports. Format: a list organized by directory."
Claude Code responds:
src/api/
auth_routes.py -> services/auth_service.py, middleware/auth.py
user_routes.py -> services/user_service.py, schemas/user.py
order_routes.py -> services/order_service.py, schemas/order.py
src/services/
auth_service.py -> repositories/user_repository.py, utils/jwt.py
user_service.py -> repositories/user_repository.py, utils/validators.py
order_service.py -> repositories/order_repository.py, services/user_service.py
payment_service.py -> services/user_service.py, clients/stripe_client.py
src/repositories/
user_repository.py -> models/user.py, config/database.py
order_repository.py -> models/order.py, models/user.py, config/database.py
src/models/
user.py -> config/database.py
order.py -> config/database.py, models/user.py
Prompt for a mermaid diagram
You to Claude Code:
> "Generate a Mermaid dependency diagram for the files
> in src/services/. Show each file as a node and each
> internal import as an arrow. Group the nodes by directory
> using subgraphs."
Expected output:
graph TD
subgraph API
AR["auth_routes"]
URR["user_routes"]
ORR["order_routes"]
end
subgraph Services
AS["auth_service"]
US["user_service"]
OS["order_service"]
PS["payment_service"]
end
subgraph Repositories
URep["user_repository"]
ORep["order_repository"]
end
AR --> AS
URR --> US
ORR --> OS
AS --> URep
US --> URep
OS --> ORep
OS --> US
PS --> US
Prompt to detect key metrics
You to Claude Code:
> "Compute the following dependency metrics for each
> Python file in src/:
> 1. Fan-out: how many internal modules it imports
> 2. Fan-in: how many internal modules import it
> 3. Fan-in/fan-out ratio
> Sort by fan-in descending. Format: table."
Claude Code responds:
| File | Fan-out | Fan-in | Ratio | Role |
|---------|---------|--------|-------|-----|
| user_service.py | 2 | 4 | 2.0 | Hub |
| user_repository.py | 2 | 3 | 1.5 | Hub |
| config/database.py | 0 | 5 | ∞ | Foundation |
| order_service.py | 3 | 1 | 0.3 | Consumer |
| payment_service.py | 2 | 0 | 0.0 | Endpoint |
How to interpret the metrics:
- High fan-in = many modules depend on it. Changes here have high impact. Handle with care.
- High fan-out = it depends on many modules. Possible god object or mixed responsibilities.
- High fan-in + low fan-out = a foundational module. Stable, many use it, but it's independent.
- High fan-out + low fan-in = a coordinator module or a possible split candidate.
- Fan-in = 0 = nobody imports it. Possible dead code or an entry point.
Detecting Problematic Patterns
Circular dependencies
A circular dependency occurs when A depends on B and B depends on A (directly or indirectly).
You to Claude Code:
> "Look for circular dependencies in this project. A circular
> dependency is when module A imports B, and B imports A (directly
> or indirectly through other modules). List all the ones
> you find."
Claude Code responds:
⚠️ Circular dependency detected:
Cycle 1: auth_service.py -> user_service.py -> auth_service.py
- auth_service imports user_service (for get_user in login)
- user_service imports auth_service (for hash_password in create)
Cycle 2: order_service.py -> payment_service.py -> order_service.py
- order_service imports payment_service (to process the payment)
- payment_service imports order_service (to update the status)
Why they're problematic:
- They make it hard to understand what depends on what
- They complicate testing: you can't test A without B, or B without A
- They complicate refactoring: you can't move A without moving B
- In Python, they can cause
ImportErrorif not handled with lazy imports
How they're resolved (Module 4 preview):
- Extract common interface: pull the shared part into a third module
- Dependency inversion: create an abstraction that both implement
- Event-based communication: A emits an event, B listens to it (no direct import)
High fan-out (too many dependencies)
You to Claude Code:
> "Identify the files with the highest fan-out (those that import the most
> internal project modules). If any imports more than 5
> different modules, flag it as a potential problem."
Claude Code responds:
⚠️ High fan-out detected:
order_service.py - Fan-out: 7
Imports: user_service, payment_service, notification_service,
order_repository, product_service, shipping_service,
config
Analysis: This module coordinates too many responsibilities.
Every change in any of the 7 modules can affect it.
Candidate to split into smaller modules.
How to interpret:
- Fan-out > 5 is a warning sign (not a rigid rule)
- Key question: "Does this module have a clear responsibility, or is it doing too many things?"
- If it's a legitimate coordinator (like an orchestrator), the fan-out may be acceptable
- If it mixes business logic with coordination, it's a split candidate
High fan-in without abstraction
You to Claude Code:
> "Identify the files with the highest fan-in (those imported
> by the most modules). If any has fan-in > 5 and isn't a
> configuration or utility module, flag it."
Claude Code responds:
📊 High fan-in:
user_service.py - Fan-in: 8
Imported by: auth_routes, user_routes, admin_routes,
order_service, payment_service,
notification_service, auth_service,
reporting_service
Analysis: This is the system's hub. Any change to
its public interface affects 8 modules. If it doesn't have a
stable and well-defined interface, every change is risky.
How to interpret:
- High fan-in isn't inherently bad — foundational modules legitimately have high fan-in
- The problem is when the module changes frequently AND has high fan-in
- Key question: "Is this module's public interface stable?"
Dependency Maps with Python Code
Sometimes you need to generate dependency maps programmatically. Here's an approach with Claude Code that combines analysis and code:
Generate an automated dependency map
You to Claude Code:
> "Write a Python script that analyzes the internal imports
> of all the .py files in src/. For each file, extract
> the 'from ... import' and 'import ...' lines that reference
> other project files (not pip packages). Output: JSON
> with the dependencies."
Claude Code generates:
# dependency_analyzer.py
import ast
import os
import json
from pathlib import Path
def get_internal_imports(file_path: str, project_root: str) -> list[str]:
"""
Extract internal imports from a Python file.
Only includes imports that reference other project files.
"""
try:
with open(file_path, "r") as f:
tree = ast.parse(f.read())
except SyntaxError:
return []
imports = []
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
if node.module and not _is_external(node.module, project_root):
imports.append(node.module)
elif isinstance(node, ast.Import):
for alias in node.names:
if not _is_external(alias.name, project_root):
imports.append(alias.name)
return imports
def _is_external(module_name: str, project_root: str) -> bool:
"""Determine whether an import is external (pip package) or internal."""
# Convert dots to path: "services.user_service" -> "services/user_service"
module_path = module_name.replace(".", "/")
# Check whether it exists as a file or directory in the project
for ext in [".py", "/__init__.py", ""]:
candidate = os.path.join(project_root, "src", module_path + ext)
if os.path.exists(candidate):
return False
return True
def build_dependency_map(project_root: str) -> dict:
"""
Build the complete map of internal dependencies.
Returns: {file: [list of files it depends on]}
"""
src_dir = os.path.join(project_root, "src")
dependency_map = {}
for py_file in Path(src_dir).rglob("*.py"):
if "__pycache__" in str(py_file):
continue
relative_path = str(py_file.relative_to(src_dir))
imports = get_internal_imports(str(py_file), project_root)
if imports:
dependency_map[relative_path] = imports
return dependency_map
def calculate_metrics(dep_map: dict) -> dict:
"""Compute fan-in and fan-out for each module."""
all_modules = set(dep_map.keys())
# Add modules that are imported but have no imports of their own
for imports in dep_map.values():
for imp in imports:
module_file = imp.replace(".", "/") + ".py"
all_modules.add(module_file)
metrics = {}
for module in all_modules:
fan_out = len(dep_map.get(module, []))
fan_in = sum(
1 for imports in dep_map.values()
if any(imp.replace(".", "/") + ".py" == module for imp in imports)
)
metrics[module] = {
"fan_out": fan_out,
"fan_in": fan_in,
"ratio": round(fan_in / fan_out, 2) if fan_out > 0 else float("inf")
}
return dict(sorted(
metrics.items(),
key=lambda x: x[1]["fan_in"],
reverse=True
))
if __name__ == "__main__":
import sys
project_root = sys.argv[1] if len(sys.argv) > 1 else "."
dep_map = build_dependency_map(project_root)
print("=== Dependency Map ===")
print(json.dumps(dep_map, indent=2))
print("\n=== Metrics ===")
metrics = calculate_metrics(dep_map)
print(f"\n{'Module':<40} {'Fan-out':>8} {'Fan-in':>7} {'Ratio':>7}")
print("-" * 65)
for module, m in metrics.items():
ratio_str = f"{m['ratio']:.1f}" if m['ratio'] != float('inf') else "∞"
print(f"{module:<40} {m['fan_out']:>8} {m['fan_in']:>7} {ratio_str:>7}")
# Expected output:
# === Dependency Map ===
# {
# "services/user_service.py": ["repositories.user_repository", "utils.validators"],
# "services/order_service.py": ["repositories.order_repository", "services.user_service"],
# ...
# }
#
# === Metrics ===
# Module Fan-out Fan-in Ratio
# -----------------------------------------------------------------
# services/user_service.py 2 4 2.0
# repositories/user_repository.py 2 3 1.5
# config/database.py 0 5 ∞
Generate mermaid from the analysis
You to Claude Code:
> "Take the dependency map we generated and convert it into a
> Mermaid diagram. Group the modules by directory using
> subgraphs. Only include dependencies with fan-in > 1 to
> keep the diagram legible."
This produces a filtered diagram that shows only the most important relationships, not all the noise of minor dependencies.
Comparison: Manual Analysis vs Claude Code
| Aspect | Manual Analysis | With Claude Code |
|---|---|---|
| Time for a 10K-line project | 4-8 hours | 15-30 minutes |
| Precision | Human (indirect imports get forgotten) | Complete (analyzes all the files) |
| Updating | Repeat the whole process | Re-run the prompt |
| Output format | Depends on the tool (whiteboard, draw.io) | Text + Mermaid (versionable) |
| Detecting circulars | Very hard manually | Automatic |
| Metrics (fan-in/fan-out) | Tedious to calculate | Instant |
| Different zoom levels | You have to redraw | New prompt, new level |
The trade-off:
- Claude Code is faster and more complete for the generation
- The human is better at the interpretation and the decisions
- The ideal workflow: Claude Code generates -> you interpret -> Claude Code adjusts based on your feedback
Connection with the Project
In the Architecture Map of a Real Project (capsule 05), the dependency map is the first component you'll generate.
You'll use this capsule's techniques to:
- Generate the dependency map at Level 1 (high-level) and Level 2 (medium) of the chosen project
- Compute fan-in and fan-out metrics
- Detect circular dependencies and high fan-out
- Document the findings with Mermaid diagrams
Everything you learn today applies directly to capsule 05.
And in Module 4 (Refactoring), the dependency maps you generate here will tell you exactly which modules to touch and in what order to refactor.
Troubleshooting
Problem 1: Claude Code generates a diagram that's too complex
Cause: The project has many modules and Claude Code tries to show them all. Solution:
You to Claude Code:
> "The diagram has too many nodes. Simplify:
> 1. Group files by directory (one node per directory)
> 2. Only show dependencies between directories, not between
> individual files
> 3. Maximum 8 nodes in the diagram"
Problem 2: Claude Code includes external dependencies (pip packages)
Cause: Claude Code doesn't correctly distinguish between internal and external imports. Solution:
You to Claude Code:
> "Only include INTERNAL project dependencies. Exclude:
> - Any import from the standard library (os, sys, json, etc.)
> - Any import from pip packages (fastapi, sqlalchemy, etc.)
> - Only show imports that reference files inside src/"
Problem 3: It doesn't detect indirect dependencies
Cause: You only look at direct imports, but A depends on B which depends on C (A depends indirectly on C). Solution:
You to Claude Code:
> "Generate the dependency map including transitive dependencies.
> If A imports B and B imports C, show that A depends
> indirectly on C. Use dotted lines for indirect
> dependencies and solid ones for direct."
Problem 4: The Mermaid diagram doesn't render correctly
Cause: Special characters in file names or syntax errors in Mermaid. Solution:
You to Claude Code:
> "The Mermaid diagram doesn't render. Check:
> 1. That node IDs don't have dots or dashes
> 2. That labels are in quotes if they have spaces
> 3. That there are no orphan nodes
> Generate a corrected version."
Problem 5: I don't know how to interpret the metrics
Cause: The fan-in/fan-out numbers have no meaning without context. Solution: Use these heuristics as a starting point:
| Metric | Normal | Attention | Problem |
|---|---|---|---|
| Fan-out | 1-3 | 4-6 | 7+ |
| Fan-in | 1-5 | 6-10 | 11+ |
| Circular deps | 0 | 1 | 2+ |
Remember: these are heuristics, not rules. A config.py module with a fan-in of 15 can be perfectly normal.
Exercises
Exercise 1: Basic Dependency Map (Easy)
Given this sample code, identify all the internal dependencies and draw the dependency map:
# File: routes/user_routes.py
from services.user_service import UserService
from schemas.user import UserCreate, UserResponse
# File: services/user_service.py
from repositories.user_repository import UserRepository
from utils.validators import validate_email
# File: repositories/user_repository.py
from models.user import User
from config.database import get_session
# File: services/auth_service.py
from services.user_service import UserService
from utils.jwt import create_token
Draw the dependency map as text (ASCII arrows) and compute the fan-in and fan-out of each module.
See solution
Dependency map:
routes/user_routes -> services/user_service
routes/user_routes -> schemas/user
services/user_service -> repositories/user_repository
services/user_service -> utils/validators
repositories/user_repository -> models/user
repositories/user_repository -> config/database
services/auth_service -> services/user_service
services/auth_service -> utils/jwt
Metrics:
| Module | Fan-out | Fan-in |
|---|---|---|
| routes/user_routes | 2 | 0 |
| services/user_service | 2 | 2 |
| services/auth_service | 2 | 0 |
| repositories/user_repository | 2 | 1 |
| schemas/user | 0 | 1 |
| utils/validators | 0 | 1 |
| models/user | 0 | 1 |
| config/database | 0 | 1 |
| utils/jwt | 0 | 1 |
Observations:
services/user_serviceis the hub (fan-in = 2, fan-out = 2)- Modules with fan-in = 0 are entry points or leaf modules
- Modules with fan-out = 0 are foundational (they don't depend on anything internal)
Explanation: The dependency map is built by following each import. Fan-out is how many modules it imports, fan-in is how many import it.
Exercise 2: Detect a circular dependency (Easy)
Look at these imports and determine whether there are circular dependencies:
# File: services/order_service.py
from services.payment_service import process_payment
from services.user_service import get_user
# File: services/payment_service.py
from services.order_service import update_order_status
from clients.stripe import charge_card
# File: services/user_service.py
from repositories.user_repository import UserRepository
# File: services/notification_service.py
from services.user_service import get_user
from services.order_service import get_order
See solution
Yes, there's a circular dependency:
order_service -> payment_service -> order_service ⚠️ CIRCULAR
order_serviceimportspayment_service(forprocess_payment)payment_serviceimportsorder_service(forupdate_order_status)
There's no circular in:
user_servicedoesn't import any other service (it's unidirectional)notification_serviceimportsuser_serviceandorder_service, but neither imports it
How to resolve the circular (preview):
- Extract
update_order_statusto a shared module - Use events:
payment_serviceemits a "payment_completed" event andorder_servicelistens to it - Dependency inversion: create an interface that both use
Explanation: The circular is detected by tracing the arrows. If you can follow a path that takes you back to the starting point, there's a cycle.
Exercise 3: Analysis with Claude Code (Medium)
Use Claude Code to analyze the dependencies of a real project. Choose a Python project you have available (it can be the one you used in Modules 1-2) and run these prompts:
- Prompt to list all the internal imports
- Prompt to generate the Level 1 Mermaid diagram
- Prompt to compute fan-in and fan-out
Document the exact prompts you used, Claude Code's responses, and your interpretation.
See solution
Example with a FastAPI project:
Prompt 1:
> "List all the Python files in src/ and for each one,
> show the internal imports (not pip packages, not stdlib).
> Format: file -> [list of imports]"
Prompt 2:
> "Generate a Mermaid diagram of the dependencies at the
> directory level. Each directory is a node. The arrows show
> whether files in one directory import files from another."
Prompt 3:
> "For each Python file in src/, compute:
> - Fan-out: how many internal files it imports
> - Fan-in: how many internal files import it
> Sort by fan-in descending. Table format."
Your interpretation should answer:
- What's the most connected module? (highest fan-in)
- Is there any module with a suspiciously high fan-out?
- Are there circular dependencies?
- Is the layer structure respected? (API -> Services -> Repositories)
Explanation: This exercise forces you to practice the complete workflow: prompt -> output -> interpretation. There's no single correct answer — it depends on the project you choose.
Exercise 4: Interpret metrics (Medium)
Given this metrics report, identify the potential problems and suggest actions:
Module Fan-out Fan-in Ratio
-----------------------------------------------------------------
services/user_service.py 2 8 4.0
services/order_orchestrator.py 9 1 0.1
config/settings.py 0 12 ∞
services/payment_service.py 3 3 1.0
services/auth_service.py 4 6 1.5
utils/helpers.py 0 7 ∞
models/base.py 0 10 ∞
services/legacy_handler.py 6 0 0.0
See solution
Problems identified:
-
order_orchestrator.py— Fan-out of 9 ⚠️- Depends on 9 different modules
- Possible god object or a module with too many responsibilities
- Action: Investigate whether it can be split into smaller orchestrators
-
user_service.py— Fan-in of 8 ⚠️- 8 modules depend on it
- Any change to its public interface affects 8 consumers
- Action: Ensure its public interface is stable and well-defined
-
legacy_handler.py— Fan-out of 6, Fan-in of 0 ⚠️- Depends on 6 modules but nobody imports it
- Possible dead code or an undocumented entry point
- Action: Verify whether it's actually used (it may be called dynamically or from tests)
-
config/settings.pyandmodels/base.py— High fan-in, fan-out 0 ✅- This is normal: they're foundational modules that many need
- No action required
-
utils/helpers.py— Fan-in of 7 ⚠️- A "helpers" module with high fan-in is often a catch-all
- Action: Investigate whether the functions should be in more specific modules
Explanation: The metrics alone don't say "there's a problem." But combined with context (what does the module do?) they let you identify suspicious patterns and prioritize investigation.
Exercise 5: Generate recommendations (Hard)
Given the following dependency map, generate a recommendations report that includes: (a) detected problems, (b) the risk level of each, (c) a suggested action for each problem.
graph TD
A["auth_service"] --> B["user_service"]
B --> A
C["order_service"] --> B
C --> D["payment_service"]
D --> C
E["notification_service"] --> B
E --> C
F["admin_service"] --> B
F --> C
F --> D
F --> E
F --> A
G["reporting_service"] --> B
G --> C
G --> D
See solution
Recommendations report:
| # | Problem | Risk | Suggested action |
|---|---|---|---|
| 1 | Circular: auth_service <-> user_service | High | Extract the shared functionality to an auth_utils module or use dependency inversion |
| 2 | Circular: order_service <-> payment_service | High | Use events: payment emits "payment_completed", order listens to it. Breaks the direct dependency |
| 3 | admin_service has a fan-out of 5 | Medium | Check whether it's a god object. Possibly split into admin_user_service, admin_order_service, etc. |
| 4 | user_service has a fan-in of 5 | Medium | Ensure a stable public interface. Consider creating an abstraction/interface if it changes frequently |
| 5 | reporting_service depends on 3 services directly | Low | Evaluate whether it should depend on repositories directly instead of services, or use a read model |
Priority order for refactoring:
- Resolve circulars (auth <-> user, order <-> payment) — they block other changes
- Evaluate admin_service — a fan-out of 5 is a sign of too many responsibilities
- Stabilize the interface of user_service — it affects 5 modules
- Evaluate reporting_service — an optimization, not urgent
Explanation: Circular dependencies are always the highest priority because they complicate all the other changes. The refactoring order is based on: (1) unblock, (2) stabilize hubs, (3) optimize.
Summary
In this capsule you learned:
- ✅ What a dependency map is and why it's the first artifact of the architecture map
- ✅ The three zoom levels: high-level (components), medium (modules), low (functions)
- ✅ How to use Claude Code to generate dependency maps in text and Mermaid
- ✅ Key metrics: fan-in (how many use you), fan-out (how many you depend on)
- ✅ Problematic patterns: circular dependencies, high fan-out, god objects
- ✅ The difference between manual analysis (hours) and with Claude Code (minutes)
- ✅ How to interpret metrics: the numbers without context say nothing, but with context they inform decisions
Next capsule: Flow Analysis — Request->Response and Data Pipelines — how to trace the path that data and requests follow through the dependencies you just mapped.
Additional Resources
- Mermaid Flowchart Syntax — Complete reference for creating flowcharts and dependency graphs in Mermaid
- Python AST Module — Official Docs — To understand how static analysis of imports works in Python
- Dependency Management in Software Architecture — Martin Fowler on dependency inversion and how to resolve problematic dependencies
- Software Design X-Rays — Adam Tornhill — Dependency analysis using version control data as a complement to static analysis
- Circular Dependencies in Python — Real Python — A practical guide on imports and how to handle circulars in Python
- C4 Model — Component Diagram — Inspiration for different zoom levels in architecture diagrams
Module 3, Capsule 02 — Refactoring & Legacy Code with Claude Code Guide