Skip to main content
Legacy Code Migration Roadmaps

The Three Legacy Dependencies That Will Block Your Migration (Fix These First)

The CTO of a mid-sized SaaS company once told me their migration to microservices was going smoothly—until the third sprint. That's when they realized their fifteen-year-old PHP monolith had three dependencies that would take down the whole project if touched wrong. A proprietary database engine with custom SQL extensions. A legacy payment API that was deprecated five years ago but still running in production. And an authentication system so tangled with business logic that separating them meant rebuilding half the app. These aren't edge cases. In my work advising teams on legacy migrations, I've seen the same three patterns block projects repeatedly. The fix isn't to rewrite everything—it's to identify and neutralize these dependencies first. Here's what they're and how to handle them. The Database Lock-In Trap Why custom SQL extensions create lock-in Picture this: your application works fine.

The CTO of a mid-sized SaaS company once told me their migration to microservices was going smoothly—until the third sprint. That's when they realized their fifteen-year-old PHP monolith had three dependencies that would take down the whole project if touched wrong. A proprietary database engine with custom SQL extensions. A legacy payment API that was deprecated five years ago but still running in production. And an authentication system so tangled with business logic that separating them meant rebuilding half the app.

These aren't edge cases. In my work advising teams on legacy migrations, I've seen the same three patterns block projects repeatedly. The fix isn't to rewrite everything—it's to identify and neutralize these dependencies first. Here's what they're and how to handle them.

The Database Lock-In Trap

Why custom SQL extensions create lock-in

Picture this: your application works fine. Queries run fast, stored procedures handle complex reporting, and the database team is happy. Then you decide to migrate to a modern cloud-native database — and everything breaks. Not because the data model is wrong, but because you leaned on a single proprietary feature that the new system simply doesn't support. I have seen teams burn three months rewriting what looked like thirty lines of SQL. That's the database lock-in trap. It hides in plain sight, often disguised as 'performance optimization' or 'database-specific convenience.'

The typical culprit is the stored procedure — but not just any stored procedure. The dangerous ones are those that use vendor-specific procedural logic: Oracle's PIPELINED functions, SQL Server's CLR integration, PostgreSQL's PL/pgSQL with heavy reliance on exception blocks, or MySQL's non-standard GROUP_CONCAT behavior. Each one ties your application logic to a specific runtime. The catch is that these extensions feel harmless during development. They save time, reduce network round-trips, and let you avoid rewriting business logic in the application layer. Until the day you need to leave.

“We moved the database first, then spent four months rewriting stored procedures we didn't even know we had. The migration stalled for a quarter.”

— Engineering lead, 2023 migration post-mortem (shared under NDA)

How to inventory your SQL usage

Start before you touch any schema. Use your database's query log or an open-source tool like pg_stat_statements or MySQL's general log — capture a full week of production traffic. Then strip out every statement that's not plain ANSI SQL. Look for syntax that feels 'magic': window functions with vendor-specific frames, temporal table queries, full-text search operators that differ across databases, or data type conversions that only work on one platform. I once found a team using PostgreSQL's tsvector directly in twenty-seven views, each depending on a custom text search configuration that had no equivalent in the target database. That hurts.

Most teams skip this step. They run a schema comparison, see that tables and indexes look similar, and assume the migration is straightforward. Wrong order. The schema is the skeleton; the query behavior is the nervous system. A single MERGE statement written in SQL Server's dialect might perform perfectly — but in PostgreSQL, that same logic requires a different approach entirely. You need to categorize every non-portable call: green (easy to replace with an application-level equivalent), yellow (requires moderate refactoring or a compatibility layer), and red (built into your data pipeline so deeply that changing it means rewriting entire services).

Strategies for unwinding database-specific calls

Once the inventory is done, the real work begins. The straightforward fix is to push logic out of the database and into the application layer. That means replacing stored procedures with service methods, moving triggers to event handlers, and rewriting vendor-specific aggregations as plain code loops. Sound expensive? It can be — but the alternative is worse: a partially migrated system where some queries still hit the old database, creating a dual-write nightmare. The trade-off here is between initial cost and long-term flexibility. Absorb the pain now, or let it compound during the migration.

A second approach is the compatibility shim. Some databases support foreign data wrappers or linked servers — you can leave a few critical stored procedures running on the old instance while the rest of the system migrates. This works for exactly one release cycle. After that, the shim becomes a permanent crutch. I have seen teams promise to 'clean it up in Q3' and then carry that dependency for two years. Not a smart bet. If you use a shim, set a hard deprecation date and assign an owner. No owner, no cleanup — guaranteed.

A blunt strategy that works: rewrite every SELECT to be a single, stateless query. No session variables, no temporary tables that depend on connection state, no commands that assume a specific isolation level. Yes, some queries will run slower. Yes, you might need to add caching or adjust indexing. But the migration succeeds because you eliminated the invisible assumptions that kept you tethered to the old database. That's the real goal — not perfection, but portability.

The Orphaned API That Refuses to Die

The Hidden Cost of a Ghost Endpoint

Most teams don't realize they have an orphaned API until the migration is already two months in. I walked into a disaster once—a fintech client migrating off a decade-old stack. Their dependency map showed clean integration points. Clean on paper, anyway. What we found was a single third-party weather API, deprecated in 2018, still being called by a background job that nobody remembered writing. That endpoint returned 200s until it didn't. Then it started returning garbage data, silently. The migration timeline stretched by six weeks because we had to unwind fifteen different code paths that had grown around that dead service like vines on a rotting fence post.

Spotting Deprecated External APIs Your Code Still Calls

The tricky bit is visibility. Your monitoring dashboard might show zero errors from a legacy external service—but that doesn't mean the data is correct or the endpoint is stable. Run a full traffic audit across staging and production environments. Search for hardcoded URLs, old vendor domains, and any HTTP call that lacks a timeout or retry policy. We fixed this by adding a single middleware layer that logged every outbound request for two weeks. We found twelve dead endpoints. Twelve. Some had been returning stale cached responses for over a year. That hurts.

Quick reality check—the cost of keeping a dead endpoint alive isn't just maintenance time. It's the compounding friction of every new developer who reads that code and assumes it's intentional. Wrong order. You think you're refactoring a module, but you're actually building on quicksand.

Odd bit about development: the dull step fails first.

When to Wrap vs. Replace a Legacy API

The natural instinct is to wrap the orphaned API in a facade and move on. Don't. Not yet. A wrapper buys you time, sure—it masks the rot. But wrappers have a nasty habit of becoming permanent infrastructure. I have seen teams spend three years maintaining a translation layer for an API that the vendor killed in month one. The catch is cost: a full replacement might take six developer-weeks, while a wrapper takes two. However, that wrapper never shrinks. It grows. Every new feature requires another adapter, another conditional, another edge case that the original API couldn't handle anyway.

Here is the pragmatic cutoff I use: if the orphaned API serves fewer than five distinct callers and returns data that can be rebuilt from your own database, replace it. If it's a black-box service that thirty microservices depend on? Wrap it—but enforce a six-month deprecation deadline with a kill switch. No extensions.

'We spent more time documenting why we couldn't remove a dead endpoint than we would have spent rewriting it from scratch.'

— Senior engineer, post-migration retrospective

The Cost of Maintaining a Dead Endpoint

What usually breaks first is not the API itself—it's the contract. The vendor silently changes a response schema, or the SSL certificate expires, or the rate limit drops from 10,000 requests per hour to 100. You catch it only when a critical batch job fails at 3 AM on a Sunday. That single incident wipes out any savings you thought you made by deferring the replacement. Most teams skip this: calculate the total cost of ownership for a legacy external dependency. Count the pager-duty rotations, the incident post-mortems, the developer hours spent validating data that nobody trusts. The number will shock you.

One team I worked with had an orphaned geocoding API that cost them $40 per month in direct vendor fees. They spent fourteen hours per quarter troubleshooting intermittent failures. That's roughly $6,000 in engineering time—every quarter—for a $40 service. They replaced it in two days. The ROI was absurd. Yet most organizations never do the math because the cost is buried in operational overhead, not a line item.

The hard truth: an orphaned API that refuses to die is usually a symptom of organizational amnesia, not technical debt. Someone knew about it once. They left. The knowledge left with them. Your migration roadmap needs a dedicated phase for hunting these ghosts—not assuming your existing documentation is honest. Because it isn't. Not yet.

The Auth System That Knows Too Much

How business logic gets baked into authentication

I once watched a team try to swap an old auth system for a new one. They expected a three-week detour. Eight months later, they were still untangling discount rules from login roles. That’s the trap—your auth system doesn’t just authenticate. It decides who sees what price, which features light up, and whether a user can downgrade mid-cycle. Somewhere, a has_role('premium_plus') check is tied to a legacy table that also controls session expiry. You pull that thread, and the whole billing module unravels. The worst part? Most engineers don’t spot this until migration week—when staging breaks and nobody knows why a retired admin can still access the discount override panel.

The catch is subtle. Early decisions seemed reasonable: store the user tier in the JWT, let the auth middleware inject role data into every request. That sounds fine until the product team adds “loyalty boost” logic—double points for users with role_id = 12. Then a support agent needs a temporary override, so someone adds an is_backdoor flag to the session object. Before you know it, your authentication layer owns eleven feature flags, three pricing overrides, and a geo-restriction rule that only runs during EU business hours. Quick reality check—none of that belongs in auth. But ripping it out means rebuilding half the application logic.

Signs your auth system is over-coupled

What breaks first is the session table. You check it during a routine audit and find columns like trial_end_date, max_child_accounts, and suppressed_email_reason. Red flag. Another tell: your login function calls a service that updates a customer segment field. That’s not authentication—that’s a CRM action wearing a mask. Most teams skip checking the logout handler. I’ve seen logout code that persists user preferences to disk, or triggers a webhook to Slack. Wrong order. Logout should kill the session, not run business analytics.

There’s a quieter sign, too. Your documentation for “user roles” is actually a feature matrix with pricing tiers. That means the auth system knows too much. It knows what a user can afford, how long they’ve been subscribed, and whether they’ve complained via support. That data should live in a billing or CRM domain, not inside a token verifier. The danger is that migrating auth becomes a full-blown feature rewrite—because every business rule you find was never designed to move.

“We thought we were replacing login. We ended up rewriting pricing, permissions, and two customer onboarding flows.”

— Engineering lead, post-migration retrospective, private conversation

Patterns to decouple auth without breaking sessions

Start with the simplest boundary: split identity from entitlements. Identity checks—are you who you say you're, does your session still live—can stay fast and stateless. Everything else moves into a lookup service. That means your new auth system returns a user ID and a token, nothing more. The first time a feature needs a role or a discount, it calls a separate entitlement API. That API might still hit the old database for now, but it’s a clean seam. You can migrate that data table by table, not all at once.

The tricky bit is keeping sessions alive during the cutover. Don’t invalidate everyone’s token on day one. Instead, issue dual tokens: the legacy token carries the old fat payload, the new token carries just an ID. Your middleware checks: if the token is old, decode the fat payload but also write a background job to migrate that user’s entitlements to the new service. Within a few weeks, most traffic hits the new path. That said, you will find edge cases—users with custom overrides that exist only as manual database edits. Log them. Migrate them by hand. Don't build a generalized migration script for ten users; it will cost you more than the manual work.

Field note: android plans crack at handoff.

One last pattern I lean on: rename the auth team. Call it the “identity” team for the duration of the project. That small language shift stops engineers from thinking they own discount logic. It also makes the boundary visible in stand-ups: “Identity is done. Entitlements is still wiring up the tier resolver.” That clarity alone saved one team I worked with from two extra sprints. The em-dash here is real—rename the service boundaries before you rename the tables.

Real-World Walkthrough: A Healthcare SaaS Migration

The PHP monolith and its three dependencies

MedSecure—a mid-sized healthcare SaaS company with 200 hospitals on contract—ran a PHP monolith that had been patched into submission for eight years. Their CTO called me in month three of a planned eighteen-month migration to Kubernetes and Go microservices. The board had approved the budget. The architecture diagrams looked clean. Then they hit sprint three and everything seized. The root cause was never the codebase size—it was three dependencies they assumed they could deal with later. Classic mistake. The database lock-in came from a custom Postgres extension that handled HIPAA audit logs—nobody had documented it. Their orphaned API fed radiology image metadata to five partner clinics that had stopped returning calls three years prior. And the auth system—oh, that one hurt. It stored role mappings, clinic affiliations, and session tokens in the same MySQL cluster, with passwords hashed using a proprietary algorithm no living engineer could reproduce.

What went wrong in sprint three

The migration team started with a low-risk billing microservice. Smooth. Sprint two handled patient intake forms. Also smooth. Then sprint three: the auth service extraction. They tried to split the MySQL cluster into a separate identity store. Thirty minutes into the migration window, the monolith stopped accepting logins. Why? The auth system’s password verification function was a stored procedure that joined across three tables—including the session table and the clinic mapping table. You couldn’t separate one without breaking the others. “We thought we’d just copy the data,” the lead engineer told me. “We didn’t realize the stored procedure was the logic.” Quick reality check—they lost a full week rolling back. Meanwhile, the radiology API started timing out because the old PHP stack was still hitting it with legacy SSL ciphers. And the Postgres audit extension? It silently failed on the new RDS instance because the extension binary was compiled for an ancient Postgres version. Three blockers, all hit in the same sprint.

The code is not the migration problem. The hidden assumptions about where the code lives are the problem.

— Engineering director, MedSecure (post-mortem notes)

How they fixed each dependency before continuing

We stopped the migration cold for two weeks. First: the auth system. We extracted password verification into a standalone Go library and tested it against every known hash in the old database—brute-force matching 14,000 accounts took one weekend. Wrong order would have been worse: if we had migrated the sessions first, we’d have orphaned live users. Instead, we moved passwords, then session logic, then role mappings—each as independent deployable units. Second: the orphaned API. We traced call logs and found that two of the five clinics had actually died—their API keys returned 404s for eighteen months. The other three were still active but using an old endpoint. We set up a proxy that logged calls for a week, then spun down the old endpoint with a clear sunset header. That alone cut monolith dependency count by one. Third: the Postgres extension. No easy fix there—we had to rewrite the audit logging as a separate service that listened to the same WAL stream. Trade-off: we lost real-time query capabilities for legacy reports, but gained full schema control on the new side. The migration restarted in week five with all three blockers cleared. They delivered the final cutover within budget, twelve days early.

Edge Cases: When These Dependencies Aren't the Problem

When the real blocker is organizational, not technical

A CTO I know spent six months untangling a database stored procedure that called seventeen different microservices. He was *sure* it was the lock-in trap. Turned out the real blockade was that the team’s release manager had a standing rule: “No deploys after 2 PM on Thursday.” That single organizational friction point killed six weeks of migration velocity. The stored procedure? Three days to refactor. The lesson: you can decouple a database faster than you can decouple a committee. If your migration stalls because QA cycles take two weeks or because the compliance officer reviews every schema change by hand, the technical dependencies are a distraction. Fix the sign-off chain first. Then touch the code.

“The worst dependency I ever removed was a release process that required three managers to send a Slack thumbs-up. Took longer than the whole database migration.”

— Systems architect, late-career consultant

What if your legacy system is actually running fine?

Not every creaky old system deserves the hammer. I have watched teams burn a full sprint ripping out an ERP integration from 2008, only to discover the replacement had fewer features and worse latency. The catch—you don’t *know* it’s running fine until you measure. Many teams mistake “it hasn’t crashed yet” for “it performs well.” Run a two-week load audit. Measure p95 response times, error rates, and—this is the blunt one—actual cost per transaction. If the legacy system is stable, cheap, and nobody on the team hates maintaining it, you might be trying to migrate a ghost. Some dependencies are safer to keep than replace, especially if your cloud migration goal is simply “lift and shift.” Migrate the data, wrap the old API with a thin adapter, and move on. The alternative is rewriting a perfectly functional black box. That hurts.

Dependencies that are safer to keep than replace

Here is where most advice gets it wrong: they assume every legacy dependency is a ticking bomb. Some dependencies are inert. Think of a financial calculation engine that has passed three decades of tax audits. Replacing that engine introduces actuarial risk far higher than any performance gain justifies. Another example—a mainframe batch job that runs for eight hours every Sunday. It’s ugly. Its COBOL is unreadable. But it *works*, and the output feeds fifteen downstream systems. Trying to rewrite that as a real-time stream? You will break something. The smarter play: treat that job as a black-box service, keep it running on the legacy hardware, and route its output through a simple connector into the new system. Not every dependency blocks migration—some are just furniture. The trick is knowing which piece of furniture is load-bearing. Wrong order. Start with the database lock-in, the orphaned API, or the auth system that knows too much—but only if those are actually what hurts. If your legacy system hums along at 20% load with zero incidents, leave it alone. You have bigger problems elsewhere. That feels unsatisfying. It’s also the cheapest decision you’ll make all year.

Limits of the 'Fix These First' Approach

When Pre-Fixing Backfires — And You Lose the Window

I once watched a team spend fourteen weeks untangling a custom authentication monolith before touching a single line of the main application. The business deadline didn't move. By the time they finished the auth cleanup, the API partner they depended on had already deprecated their v1 endpoints. The dependency they should have addressed first — the orphaned API — was still there, now harder to reach because the auth layer they'd just rebuilt didn't talk to the deprecated endpoint gracefully. They fixed the wrong lock first. That hurts more than fixing nothing at all.

The trap is subtle: you see a tangled dependency and assume it must be the root of all migration pain. Often it's just the loudest. Any fix that takes longer than the remaining migration window is not a fix — it's a separate project. Quick reality check — if cleaning up the legacy auth system runs six weeks and your full migration has to ship in eight, you have no buffer for the database extraction or the API replacement. One slipped dependency kills the whole plan. Most teams skip asking: does this fix actually unblock the next step, or does it just feel productive?

Risks of Over-Engineering the Dependency Removal

Another pattern I see: teams treat legacy dependencies like ticking bombs that require surgical extraction. They build adapters, write translation layers, implement feature-flag toggles for every edge case. The result is a migration that ships with three times the surface area of the original system. You didn't remove the legacy dependency — you wrapped it in so much infrastructure that the old code now runs behind a dozen abstraction walls, each one a potential failure point.

The trade-off between a full rewrite and a pragmatic wrapper is real. Sometimes wrapping an old auth system behind a lightweight facade — five hundred lines of routing logic, not five thousand — buys you two years of breathing room. The rewrite crowd will call it technical debt. The delivery crowd will call it shipping. I lean toward the latter when the legacy code is stable, well-understood, and not actively blocking feature work. The ideal is removal. The practical is containment with a clear expiration date.

Reality check: name the development owner or stop.

'We spent six months building the perfect adapter. Then the legacy system's vendor went bankrupt, and we had to throw the entire wrapper away.'

— Principal engineer at a mid-market logistics firm, reflecting on a 2022 migration postmortem I sat in on

Trade-Offs Between Rewrite and Wrap

Deciding between a full rewrite and a strategic wrapper comes down to three signals: change frequency, surface area, and team familiarity. If the legacy dependency changes rarely — think a billing engine that processes the same three invoice formats it did in 2017 — a wrapper is usually smarter. If that same system has twelve undocumented endpoints and nobody on the team understands its error handling, a wrapper just postpones the pain. Wrong order kills you either way.

The worst outcome is neither rewrite nor wrap — it's the stasis zone where you start both approaches, stall on both, and end up with half a wrapper and half-rewritten core logic that don't connect. I have seen teams spend eighteen months in that zone. They emerged with a system that was still dependent on the original database, still calling the orphaned API, and still authenticating through the monolith they'd meant to replace. The dependency map hadn't changed. The code was just rearranged. That's not a migration. That's shuffling deck chairs while the platform sinks. Fix these first — but fix them in the order that actually lets you ship the rest.

Frequently Asked Questions About Legacy Dependencies

How do I find hidden dependencies in my codebase?

Start with the build graph, not the source code. I have seen teams waste two weeks grepping for import statements while a rogue Docker image or a cron job that pokes a legacy REST endpoint sits completely invisible. Run a static analysis tool that traces runtime linkages — something like jdepend for Java or pipdeptree with environment markers for Python. Then cross-check that output against your deployment manifests. The real hidden dependency is often the one nobody documented because “everyone knows it’s there.” Nobody knows. That hurts.

Most teams skip this: pull your last year of production logs and filter for 404 or 503 responses that hit endpoints you thought were dead. A retiring API that still gets called quarterly by a batch job is a time bomb — it works fine until the certificate rotates or someone finally kills the box. One client found a dependency this way: a nightly FTP transfer from a mainframe that triggered a legacy SOAP call. No code referenced it. No diagram showed it. The ops runbook mentioned it in a footnote. The footnote saved the migration.

“Every unmapped dependency is a deferred outage. Deferrals compound — they don’t disappear.”

— Engineering lead, post-mortem after a 14-hour rollback

Should I fix dependencies before or during migration?

Fix the structural ones before, fix the cosmetic ones during. A database lock-in or an auth monolith that injects itself into every transaction — those will corrupt your migration timeline repeatedly if you try to handle them in-flight. We fixed this by isolating the auth dependency into a sidecar proxy before touching a single table migration. Took eight days. Cut total migration risk by half. Wrong order — fixing the auth system concurrently with a data move — and you lose a day every time the token validation schema shifts.

The catch is that some dependencies look structural but behave cosmetic. A reporting API that only serves a PDF once per month? That’s a during-migration candidate. Extract it into a façade, migrate the core, and then rewrite the PDF endpoint at your leisure. But a payment gateway that every microservice calls synchronously? That's a before-migration dependency. Decouple it first or the seam blows out under load. Trade-off: you spend upfront effort on something that might change anyway, but you avoid the far worse cost of a mid-migration rollback that resets three months of work.

What if the dependency is owned by another team?

Treat them as a foreign system with a weak SLA — because that’s what they're, even if you share a Slack channel. Get a written contract for the dependency’s interface stability during your migration window. One sentence: “We won't change endpoint signatures or response schemas between May 1 and August 31.” That sounds fine until their sprint backlog overrides your needs. Quick reality check — schedule a joint incident-dress rehearsal where your migration triggers a fallback path and they have to prove their API still works under that fallback scenario. Most teams skip this: they discover the other team’s dependency has an undocumented rate limit only when their batch migration script starts getting 429 responses at 2 AM.

If the owning team refuses to commit to stability, you have two moves. First, wrap their dependency in a local adapter with circuit-breaking logic so your migration can tolerate outages. Second, buffer the data locally — a read-replica, a snapshot cache — so you aren’t calling their production service on every migration step. Neither is ideal: adapters add latency, caches add staleness. But a degraded migration that finishes is infinitely better than a pristine migration that never completes because a cross-team dependency changed without notice. End with that trade-off visible on your roadmap — not as a risk, as a decision.

Next Steps for Your Migration Roadmap

Audit your codebase this week

Pick one afternoon. Block your calendar. Open your dependency graph and look for the three patterns we just covered: a database driver pinned to a specific vendor extension, an API endpoint with no version header and zero test coverage, and an auth module that knows the user's shoe size. I have walked teams through this exact audit—and every single time, the blocker they *thought* was hardest (the database) turned out to be easier than the auth system. The catch? Most teams skip the audit because they assume they already know what they have. They don't. Run npm list | grep mysql on a ten-year-old Node app; you'll find three different connectors, two of them deprecated, one pulling a native binary that won't compile on ARM. That hurts.

Write down every dependency that touches persistence, external I/O, or session state. Then annotate each one with its risk profile: locked to a single vendor, unmaintained but stable, or healthy. Wrong order here—auditing after you start sprint planning—wastes dev cycles. Do the inventory first. It takes four hours, not four weeks.

Decide: replace, wrap, or keep?

Not every legacy dependency demands a rewrite. That orphaned API you found—the one that "refuses to die"—might be fine behind a strangler facade for eighteen months. The auth system that knows too much, however, rarely survives partial extraction. I have seen teams burn six months trying to wrap it, then scrap the wrapper and replace it in eight weeks. The decision heuristic is brutal but honest: can you isolate the dependency behind a clean interface without modifying it? If yes, wrap it. If the interface leaks vendor-specific types or auth tokens into every caller, replace it. If the thing is used by exactly one screen and scheduled for deprecation next quarter? Keep it—but write a hard deadline in your roadmap. "We'll fix this later" is not a decision; it's a deferral with interest.

Most teams overestimate their ability to wrap a messy dependency and underestimate the cost of keeping it. The wrapper becomes the new legacy if the interface is leaky.

— Lead platform engineer, after unwrapping a four-year-old auth facade that sneaked session IDs into log lines

Set dependency milestones before the first sprint

Most migration roadmaps look like a waterfall dressed in Kanban clothes: "Sprint 1-3: refactor ORM, Sprint 4-6: extract auth." That order works if your dependencies are independent. They aren't. The database lock-in trap often hides inside the auth module—because the auth system stores user profiles in vendor-specific JSON columns. You fix the auth module first, and suddenly the database migration gets easier because no code reads those custom fields anymore. The trick is to map *dependency chains*, not just a list of work items. Draw a directed graph. Which dependency blocks two others? Extract that one first, even if it feels harder. I tell teams to set three concrete milestones before they write their first user story:

  • Milestone A: All external dependencies (DB, API, auth) have documented exit criteria.
  • Milestone B: The longest chain of blocked dependencies is broken.
  • Milestone C: No new code calls the legacy interface—only the adapter layer does.

That's the roadmap skeleton. Everything else is task management. Pour your energy into making Milestone A real: talk to the teams that own those dependencies, find out whether they plan to deprecate the endpoint next quarter or keep it alive for five more years. One conversation can save you a sprint of wasted adapter code. Start that conversation today—not next sprint, not after the kickoff meeting. Today.

Share this article:

Comments (0)

No comments yet. Be the first to comment!