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_service depends on user_service, payment_repository, and stripe_client — it has a fan-out of 3
  • user_service is imported by payment_service, notification_service, auth_service, and order_service — it has a fan-in of 4
  • user_service is 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:
FunctionTypeCalled fromDescription
get_user(user_id)Publicauth_service, order_service, admin_routesGets a user by ID
create_user(data)Publicauth_service, user_routesCreates a new user
update_user(user_id, data)Publicuser_routes, admin_routesUpdates user data
delete_user(user_id)Publicadmin_routesDeletes a user (soft delete)
get_user_with_orders(user_id)Publicorder_service, admin_routesUser 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 ImportError if 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

AspectManual AnalysisWith Claude Code
Time for a 10K-line project4-8 hours15-30 minutes
PrecisionHuman (indirect imports get forgotten)Complete (analyzes all the files)
UpdatingRepeat the whole processRe-run the prompt
Output formatDepends on the tool (whiteboard, draw.io)Text + Mermaid (versionable)
Detecting circularsVery hard manuallyAutomatic
Metrics (fan-in/fan-out)Tedious to calculateInstant
Different zoom levelsYou have to redrawNew 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:

MetricNormalAttentionProblem
Fan-out1-34-67+
Fan-in1-56-1011+
Circular deps012+

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:

ModuleFan-outFan-in
routes/user_routes20
services/user_service22
services/auth_service20
repositories/user_repository21
schemas/user01
utils/validators01
models/user01
config/database01
utils/jwt01

Observations:

  • services/user_service is 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_service imports payment_service (for process_payment)
  • payment_service imports order_service (for update_order_status)

There's no circular in:

  • user_service doesn't import any other service (it's unidirectional)
  • notification_service imports user_service and order_service, but neither imports it

How to resolve the circular (preview):

  1. Extract update_order_status to a shared module
  2. Use events: payment_service emits a "payment_completed" event and order_service listens to it
  3. 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:

  1. Prompt to list all the internal imports
  2. Prompt to generate the Level 1 Mermaid diagram
  3. 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:

  1. 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
  2. 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
  3. 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)
  4. config/settings.py and models/base.py — High fan-in, fan-out 0 ✅

    • This is normal: they're foundational modules that many need
    • No action required
  5. 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:

#ProblemRiskSuggested action
1Circular: auth_service <-> user_serviceHighExtract the shared functionality to an auth_utils module or use dependency inversion
2Circular: order_service <-> payment_serviceHighUse events: payment emits "payment_completed", order listens to it. Breaks the direct dependency
3admin_service has a fan-out of 5MediumCheck whether it's a god object. Possibly split into admin_user_service, admin_order_service, etc.
4user_service has a fan-in of 5MediumEnsure a stable public interface. Consider creating an abstraction/interface if it changes frequently
5reporting_service depends on 3 services directlyLowEvaluate whether it should depend on repositories directly instead of services, or use a read model

Priority order for refactoring:

  1. Resolve circulars (auth <-> user, order <-> payment) — they block other changes
  2. Evaluate admin_service — a fan-out of 5 is a sign of too many responsibilities
  3. Stabilize the interface of user_service — it affects 5 modules
  4. 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

  1. Mermaid Flowchart Syntax — Complete reference for creating flowcharts and dependency graphs in Mermaid
  2. Python AST Module — Official Docs — To understand how static analysis of imports works in Python
  3. Dependency Management in Software Architecture — Martin Fowler on dependency inversion and how to resolve problematic dependencies
  4. Software Design X-Rays — Adam Tornhill — Dependency analysis using version control data as a complement to static analysis
  5. Circular Dependencies in Python — Real Python — A practical guide on imports and how to handle circulars in Python
  6. C4 Model — Component Diagram — Inspiration for different zoom levels in architecture diagrams

Module 3, Capsule 02 — Refactoring & Legacy Code with Claude Code Guide