Migration Strategies Reference
Strategy Selection Decision Tree
Is the system modular with clear boundaries?
├─ Yes → Can old and new run simultaneously?
│ ├─ Yes → Strangler Fig
│ └─ No → Branch by Abstraction
└─ No
Is the scope small (<50 files) with high test coverage (>80%)?
├─ Yes → Big Bang (with feature flag)
└─ No
Is correctness critical (financial, medical)?
├─ Yes → Parallel Run
└─ No → Strangler Fig (create boundaries first)Strangler Fig Pattern
Replace a system incrementally by routing traffic to new implementations while old code remains operational.
When to use
- System has identifiable entry points (APIs, routes, event handlers)
- New implementation can coexist with old
- Gradual rollout is acceptable
Implementation steps
- Identify boundary — find the seam where old and new can coexist
- Create facade/proxy — route requests to either old or new implementation
- Build new implementation — implement one feature/route at a time
- Route traffic — shift traffic gradually via feature flags or routing rules
- Verify equivalence — compare outputs of old and new
- Remove old code — only after new is verified in production
Routing mechanisms
// Feature flag-based routing
function handleRequest(req: Request): Response {
if (featureFlags.isEnabled('new-auth-service', req.userId)) {
return newAuthService.handle(req);
}
return legacyAuthService.handle(req);
}
// Percentage-based rollout
function routeTraffic(req: Request): Response {
const percentage = featureFlags.getPercentage('new-payment');
if (hashUserId(req.userId) % 100 < percentage) {
return newPaymentService.handle(req);
}
return legacyPaymentService.handle(req);
}Risk mitigation
- Start with lowest-risk, highest-value routes
- Monitor error rates per route during cutover
- Keep rollback instant (flip flag, not redeploy)
Branch by Abstraction
Introduce an abstraction layer over the code to be replaced, then swap the implementation behind it.
When to use
- Deeply coupled internal code (shared libraries, utility functions)
- No clear request routing boundary
- Need to maintain single deployable artifact
Implementation steps
- Create abstraction — interface/adapter over the code to be replaced
- Adapt existing code — make old code implement the new interface
- Build new implementation — implement the interface with new technology
- Switch implementation — swap via dependency injection or config
- Remove old code — after new implementation is verified
Example
// Step 1: Create abstraction
interface UserRepository {
findById(id: string): Promise<User>;
save(user: User): Promise<void>;
}
// Step 2: Adapt old code
class LegacyUserRepository implements UserRepository {
async findById(id: string): Promise<User> {
return this.legacyORM.query(`SELECT * FROM users WHERE id = ?`, [id]);
}
}
// Step 3: New implementation
class PrismaUserRepository implements UserRepository {
async findById(id: string): Promise<User> {
return this.prisma.user.findUnique({ where: { id } });
}
}
// Step 4: Switch via DI
const userRepo: UserRepository = config.useNewDB
? new PrismaUserRepository(prisma)
: new LegacyUserRepository(legacyORM);Parallel Run
Run old and new systems simultaneously, compare outputs, use old for production until confidence is high.
When to use
- Correctness is critical (financial calculations, compliance)
- Need statistical confidence before cutover
- Can afford the computational overhead of running both
Implementation steps
- Instrument both paths — run old and new for every request
- Compare outputs — log differences, don't fail on mismatch
- Analyze discrepancies — fix new implementation until match rate > threshold
- Cutover primary — switch new to primary, old to shadow
- Remove shadow — after confidence period
Comparison framework
async function parallelRun<T>(
label: string,
control: () => Promise<T>,
candidate: () => Promise<T>,
compare: (a: T, b: T) => boolean
): Promise<T> {
const controlResult = await control();
// Run candidate async, don't block
candidate().then(candidateResult => {
const match = compare(controlResult, candidateResult);
metrics.record('parallel_run', {
label,
match,
control: controlResult,
candidate: candidateResult,
});
}).catch(err => {
metrics.record('parallel_run_error', { label, error: err.message });
});
return controlResult; // Always return control (old) result
}Big Bang Migration
Replace everything at once. Use only when scope is small and well-understood.
When to use
- Scope < 50 files
- Test coverage > 80%
- No external API consumers
- Team can dedicate a sprint to the migration
Risk mitigation
- Feature flag the entire migration behind a single toggle
- Run full test suite against both branches
- Prepare rollback script that reverts in < 5 minutes
- Schedule during low-traffic window
Risk Assessment Matrix
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Data loss during migration | Low | Critical | Backup before each phase, verify row counts |
| Breaking API consumers | Medium | High | Versioned endpoints, deprecation notices, adapter layer |
| Performance regression | Medium | Medium | Benchmark before/after, set performance budgets |
| Feature parity gap | High | Medium | Checklist per feature, automated comparison tests |
| Rollback failure | Low | Critical | Test rollback procedure before production migration |
| Extended migration stall | Medium | High | Time-box each phase, define go/no-go criteria |
Migration Plan Template
# Migration Plan: [From] → [To]
## 1. Scope
- **Files affected:** [count]
- **Modules affected:** [list]
- **External consumers:** [count and names]
- **Test coverage:** [percentage]
## 2. Strategy
- **Pattern:** [Strangler Fig | Branch by Abstraction | Parallel Run | Big Bang]
- **Rationale:** [why this strategy]
## 3. Phases
| Phase | Scope | Duration | Rollback | Go/No-Go |
|-------|-------|----------|----------|----------|
| 1 | [description] | [estimate] | [mechanism] | [criteria] |
| 2 | [description] | [estimate] | [mechanism] | [criteria] |
## 4. Risk Matrix
| Risk | L | I | Score | Mitigation |
|------|---|---|-------|-----------|
## 5. Verification
- [ ] Before-snapshot tests created
- [ ] After-migration regression suite
- [ ] Performance benchmarks captured
- [ ] Behavioral equivalence verified
- [ ] Rollback tested
## 6. Rollback Plan
- **Trigger:** [when to rollback]
- **Procedure:** [step-by-step]
- **Estimated time:** [duration]
- **Data reversion:** [if applicable]Monolith Decomposition Guide
Domain-Driven Boundary Detection
- Identify bounded contexts via data ownership analysis
- Map inter-module communication (sync calls, shared DB tables, events)
- Score coupling: Low (events only) → Medium (API calls) → High (shared state)
- Prioritize extraction: start with lowest-coupling, highest-value domains
Extraction Sequence
- Extract shared data → service with API
- Add anti-corruption layer at monolith boundary
- Dual-write during transition (old DB + new service)
- Verify data consistency
- Cut over reads to new service
- Cut over writes to new service
- Remove monolith code + old DB tables
Agent Teams Aptitude (SKILL.md excerpt)
Shift meets all three subagent criteria — use Pattern D: Specialist Team (2-3 workers) for large migrations:
| Worker | Ownership | Task |
|---|---|---|
codemod-writer |
codemods/**, transforms/** |
Generate and test codemod scripts |
migration-verifier |
tests/migration/** |
Write before/after behavioral equivalence tests |
db-migrator (optional) |
migrations/** |
Schema expand-contract scripts when DB migration is in scope |
Spawn when: migration touches ≥3 independent subsystems (e.g., API + DB + frontend) and codemod generation, test creation, and schema work can proceed in parallel. Do not spawn for single-module upgrades (<50 files).
Overlap Boundaries (full list)
SKILL.md keeps only the vs Zen and vs Gear lines inline (the two most-confused agents); the rest lives here.
- vs Zen: Zen = refactor for readability without changing behavior; Shift = migrate to new APIs, frameworks, or versions.
- vs Launch: Launch manages version releases; Shift orchestrates cross-version migration with compatibility layers.
- vs Schema: Schema designs new schemas; Shift orchestrates schema evolution and data migration between versions.
- vs Builder: Builder = implement business logic; Shift = design migration transforms that Builder executes.
- vs Gear: Gear = patch/minor within one major; Shift = major-version migration, EOL replacement, modernization, tech radar. Gear escalates to
detectwhen a patch reveals deeper need. - vs Sentinel: Sentinel fixes specific vulnerabilities and owns SAST findings; Shift modernizes and evaluates dependency-level supply-chain risk (
radarchecks provenance and trust posture). - vs Chain:
malware-scanowns active malware/worm IoC forensics;intakeowns skill/plugin/MCP manifests.radarcovers preventive provenance posture. - vs Magi: Magi = multi-stakeholder tech decision arbitration. Shift's
radarprovides the technical evidence; Magi makes the organizational decision.