Cut Migration Risk With Dependency First Code Mapping for IBM i Teams

  • September 10, 2026
  • Ty Woods
  • 13 min read

Code dependency mapping is the practice of documenting every relationship a piece of software has, from imports and function calls to database tables and third-party services, so engineers know exactly what will break before they change anything. The primary payoff is a smaller blast radius during refactors and migrations: instead of guessing what a change might touch, you’re working from evidence. The best maps combine static analysis of the code itself with runtime signals that show what actually happens when the system runs.


TL;DR:

  • Transitive dependencies and cross-repo relationships pose the greatest risks, often requiring runtime telemetry and CI metadata to uncover hidden connections.
  • Building reliable dependency maps involves combining static analysis with runtime tracing and validation, with AI tools serving as accelerators rather than standalone solutions.
  • Visualizing dependencies through layered, aggregate layouts reduces clutter, helping engineers quickly identify architecture violations and plan refactors.
  • Prioritizing low fan-in modules for initial refactoring minimizes blast radius and builds confidence, while high fan-in modules demand cautious, well-tested approaches.
  • Maintaining map accuracy requires incremental discovery and verification, as codebase changes quickly cause maps to drift unless actively managed.

Golden Path Digital
goldenpathdigital.com
Map IBM i Dependencies Before Modernizing
Golden Path Digital analyzes IBM i RPG codebases with AS/Forward, helping teams understand dependencies before applying modernization strategies.

Explore AS/Forward

Table of Contents

What Does a Code Dependency Map Actually Need to Capture?

A dependency graph is only useful if it captures the relationships that actually cause outages when broken. Scope matters more than tooling here. A map that only tracks imports gives you a partial picture; a map that tracks how data actually moves gives you something you can plan a migration around.

At minimum, a working code dependency graph should record:

  • Static imports and module references: the compile-time or load-time connections between files
  • Function and method call graphs, who calls what, and how deep the chains run
  • Transitive and third-party libraries: dependencies pulled in indirectly through other packages
  • Runtime and service calls: API requests, message queue events, and inter-service traffic
  • Data and configuration dependencies: shared database tables, environment variables, and config files that couple systems that look unrelated in the code

Static analysis alone misses dynamic behavior like reflection or conditional service calls that only appear at runtime. Runtime tracing alone misses dead code and unused branches that still carry risk. AWS’s documentation on dependency mapping makes the same point: the most reliable maps blend static code analysis with runtime telemetry such as distributed tracing and network flow logs, because each source catches what the other misses.

Which Dependency Types Drive the Most Risk?

Not every dependency deserves equal attention. Direct dependencies are the ones your code explicitly imports or calls, and they’re usually well documented. Transitive dependencies, the packages your dependencies rely on, are where trouble hides. A single library update can quietly pull in a dozen sub-dependencies nobody on the team has reviewed.

Fan-in and fan-out counts give you a fast way to triage. Fan-in measures how many other modules depend on a given piece of code; high fan-in means changing it is risky because the blast radius is wide. Fan-out measures how many things a module depends on; high fan-out often signals a module that’s doing too much and is a good refactor candidate. Sequencing refactors by starting with low fan-in modules is a practitioner-tested approach to safe modernization that keeps early wins low risk while the team builds confidence in the map.

A 2024 user study of 20 developers found that visualizing transitive dependencies changed how engineers prioritized security fixes. Text-based tools like npm audit list vulnerable packages, but they don’t show how deeply buried those packages are. Once developers saw the graph, they reprioritized updates that textual audits had ranked lower.

Cross-repo and cross-platform dependencies add another layer of difficulty. When a service in one repository calls an API defined in another, or when an IBM i job stream feeds data into a separate Laravel application, no single codebase tells the whole story. You need CI metadata, network telemetry, or both to close that gap.

How Do You Build Dependency Maps at Scale?

Building a map that survives contact with a real codebase takes more than pointing a tool at your repository and hoping. Four signal sources do most of the work, and each has a different failure mode.

  1. Static analysis first. Parsing manifests, import statements, and call graphs (the same category of output you’d get from a jdeps-style tool) reliably finds compile-time relationships. It’s fast, repeatable, and catches almost everything that doesn’t depend on runtime conditions.
  2. Runtime tracing for the rest. Distributed tracing and application telemetry reveal what actually executes: call frequency, latency, and paths that only trigger under specific conditions. This is the layer that catches dynamic dispatch and conditional logic static analysis can’t resolve.
  3. CI and build metadata as a discovery signal. Artifact lineage, build logs, and deployment pipelines often reveal dependencies nobody documented, especially in older systems where the build process is the only remaining record of how pieces fit together.
  4. Network telemetry for service boundaries. When dependencies cross service or repository lines, packet-level or API-gateway logs are often the only reliable evidence of who’s actually calling whom.

AI-assisted enrichment has a real role here, particularly for legacy codebases where documentation is thin, as explained in this RAG Architecture for Engineers: A Practical Guide. AI can accelerate call-level enrichment and extract embedded SQL from procedural code, turning weeks of manual tracing into days. But practical guides on building AI-assisted dependency maps are consistent on the limits: AI tools miss dynamic dispatch, reflection, and anything resolved only at runtime. Every AI-generated map needs validation from someone who actually knows the system.

Before you trust any map enough to plan a migration around it, run it through a verification checklist: sample a handful of traced call paths and confirm them manually, have module owners review the sections that touch their code, and validate incrementally rather than certifying the whole map at once.

Pro Tip: Don’t try to validate an entire enterprise map in one pass. Pick the three modules you’re refactoring next quarter, verify those completely, and expand outward. A partially verified map you trust beats a fully generated map you don’t.

What Visualization Patterns Actually Reduce Clutter?

A dependency graph with 4,000 nodes and every possible edge drawn is not a map. It’s noise. The layout decision that matters most is when to aggregate and when to drill down.

Project or package-level aggregation works for architecture reviews and initial planning; you want to see how big chunks of the system relate without drowning in function-level detail. Function-level call graphs matter only once you’ve picked a specific module to refactor and need to see exactly what breaks inside it.

Layered layouts, sometimes called Software City patterns, address this by grouping dependencies into levels rather than drawing every arc directly. A study published in SN Computer Science tested this approach against classic layouts on a real-world project and found that layered visualization reduced clutter and made architecture violations easier to spot, because dependency direction gets encoded in the layout itself rather than in a tangle of crossing lines.

A few UI conventions consistently make maps more usable in practice:

  • Filters that let you isolate by module, team, or dependency type
  • A legend explaining what link width or color represents (call frequency, criticality, or data volume)
  • “New graph from selection” workflows that let engineers drill from a high-level view into a focused subgraph without losing context

The real test of a visualization isn’t whether it looks impressive. It’s whether it helps someone answer a task-based question faster, like which service to check before a vulnerability patch or which module to refactor first. Research on enhanced dependency visualization found engineers using improved visual layouts completed refactoring tasks with higher accuracy and modestly faster than those using traditional layouts, which is the kind of measurable outcome that justifies the visualization investment in the first place.

How Do You Turn a Dependency Map Into a Migration Plan?

A map that sits in a wiki page helps nobody. The value shows up when it drives an actual workflow, and that workflow follows a consistent loop.

  1. Inventory every service, module, and repository in scope, even the ones nobody’s touched in years.
  2. Discover relationships using static analysis, runtime tracing, and CI metadata together.
  3. Validate the map against SME knowledge and sampled traces before trusting it for planning.
  4. Prioritize using fan-in, fan-out, and transitivity. Low fan-in modules make safer first targets because fewer things depend on them; high fan-in, high fan-out modules need the most caution and the most test coverage.
  5. Execute refactors or migration waves in the sequence the map suggests, not the sequence that feels intuitive.
  6. Monitor for drift, because every merged pull request is a chance for the map to fall out of sync with reality.

This loop does double duty for migration planning and regression testing. If you’re moving a monolith to microservices or moving an IBM i RPG application toward a modern architecture, the map tells you which modules can move independently and which are so tightly coupled they need to move together. Industry guidance on application dependency mapping for legacy modernization treats this sequencing step as foundational, not optional, precisely because skipping it is how migrations turn into multi-quarter fire drills.

The map also defines your regression test scope. Instead of re-testing the entire application after every change, you test the modules the map shows are actually connected to what changed. That’s a meaningfully smaller, more defensible test suite.

Pro Tip: Add a lightweight dependency check to your pull request process. Even a simple rule, flag any PR that touches a module with high fan-in for an extra reviewer, catches a surprising number of regressions before they ship.

Why Do Dependency Maps Go Stale, and How Do You Keep Them Useful?

Maps drift because codebases change faster than documentation does. Every new pull request is a small chance for the map to fall out of date, and in a codebase with dozens of engineers committing weekly, that adds up fast. Architecture diagrams drift toward fiction for the same reason. What you documented six months ago described the system’s intended structure. What runs in production today is the system’s actual runtime reality, and those two things diverge more than most teams admit.

A few failure modes show up repeatedly:

  • Dynamic dispatch and reflection hide relationships that only resolve at runtime, so static analysis alone will never fully capture them.
  • Cross-repo dependencies create blind spots when nobody owns the connection between two codebases; CI metadata and runtime probes are usually the only way to bridge that gap.
  • Legacy systems with sparse documentation force teams to rely more heavily on runtime evidence, since the code itself won’t explain intent that was never written down.
  • Overambitious first attempts try to map everything at once, which produces a graph too dense to use and a team too discouraged to maintain it.

The fix for most of this is starting lightweight. Build a module-level map first, and add function-level call graphs only for the specific modules you’ve chosen to refactor next. Continuous, incremental discovery beats a single heroic mapping effort that nobody updates again.

How Golden Path Digital Approaches Dependency-First Modernization

Every effective modernization engagement sequences around dependency mapping before any code gets touched, which is the same discipline this guide has walked through. For IBM i teams, AS/Forward parses and analyzes RPG codebases to surface call chains, copy members, and data dependencies that decades of undocumented changes have buried. For Laravel teams, Laravel Ascend applies the same mapping discipline before automating version upgrades.

That sequencing supports:

  • Scoped migration waves instead of all-at-once rewrites
  • Reduced consulting hours because engineers aren’t re-discovering the system from scratch
  • Faster technical debt reduction, with some IBM i engagements cutting RPG technical debt within a 90-day window

Teams with in-house static analysis skills and time can build initial maps themselves. Teams facing decades of undocumented RPG or a stalled Laravel upgrade path often get further faster with a structured modernization assessment that starts with mapping instead of guessing.

A Pragmatic Take on Where to Invest Mapping Effort

Most teams over-invest in function-level call graphs before they’ve earned the need for that detail. In your first two weeks, find your lowest fan-in modules, add one CI check for your highest blast-radius service, and start a simple verification board where owners sign off on map sections. That’s more useful than a perfect diagram nobody has checked.

Ty

Ready to Map Your Codebase Before You Modernize It?

There are alternatives to open-ended consulting hours spent rediscovering a codebase from scratch. Some vendors start every engagement with structured dependency mapping baked into the tooling itself, so you pay for a scoped plan instead of trial-and-error discovery.

Golden Path Digital

That’s the practical difference for a team staring down an IBM i system nobody’s fully documented or a Laravel application stuck on an old version. Modernization tools typically build the dependency map first, then apply automation against a verified picture of what the system actually does. If your team is weighing whether to build this discovery process in-house or bring in a service that’s already built it, the fastest way to find out is to request a legacy code modernization assessment and see what a dependency-first scope actually looks like for your codebase.

Sources

FAQ

What Is Code Dependency Mapping?

It’s the process of documenting how code, services, and data connect within a system, combining static analysis with runtime signals to show both intended and actual relationships.

What’s the Difference Between Static and Dynamic Dependency Analysis?

Static analysis examines code and manifests without running the program, catching imports and call structures; dynamic or runtime analysis traces what actually executes, catching conditional and reflective behavior static tools miss.

How Do Fan-In and Fan-Out Help With Refactor Sequencing?

High fan-in modules carry more risk because more code depends on them, while high fan-out modules often signal overloaded components worth simplifying; sequencing refactors from low fan-in outward reduces blast radius.

Can AI Tools Build a Dependency Map on Their Own?

AI can accelerate call-level enrichment and SQL extraction from legacy code, but it misses dynamic dispatch and reflection, so every AI-generated map still needs validation from engineers who know the system.

Does Golden Path Digital Offer Dependency Mapping for Legacy Systems?

Yes. AS/Forward maps IBM i RPG codebases and Laravel Ascend applies the same dependency-first approach before automating Laravel framework upgrades.