Migrating Code
Core Principles
-
Never break production - Backward compatible until fully rolled out
-
Small, reversible steps - Each step independently deployable
-
Test at every stage - Before, during, and after
-
Have rollback ready - Always
Migration Checklist
- Pre-Migration: Read changelog, identify breaking changes, ensure test coverage
- During: Small steps, test each, monitor errors, rollback ready
- Post: Verify tests, check metrics, remove scaffolding, update docs
Database Schema
Safe Patterns
Operation Pattern
Add column Add nullable first → backfill → add constraints
Remove column Stop writes → deploy code that doesn't read → drop column
Rename column Add new → dual-write → backfill → switch reads → drop old
Change type New column → dual-write → migrate in batches → switch → drop
Never: Add NOT NULL without defaults to tables with data.
API Migrations
Deprecation Process
-
Add deprecation warnings to old endpoints
-
Document migration path
-
Set and communicate sunset date
-
Monitor usage
-
Remove after usage drops
{ "data": {}, "_warnings": [{ "code": "DEPRECATED_ENDPOINT", "message": "Use /api/v2/users instead", "sunset": "2025-06-01" }] }
Framework Upgrades
-
Upgrade to latest minor first - Get deprecation warnings
-
Fix warnings - Before major upgrade
-
One major at a time - Don't batch
-
Test after each step
Adapter Pattern for Library Swaps
// Wrap library usage // lib/date.ts import moment from 'moment'; export const formatDate = (date: Date, format: string) => moment(date).format(format);
// Migration: just change the adapter import { format } from 'date-fns'; export const formatDate = (date: Date, fmt: string) => format(date, fmt);
Gradual Rollout
Use feature flags:
if (featureFlags.useNewSystem) { return newService.process(order); } else { return legacyService.process(order); }
Roll out: 1% → 10% → 50% → 100% → remove flag
Common Pitfalls
Avoid:
-
Big bang migrations
-
No rollback plans
-
Skipping dual-write phase
-
Single large data transactions
-
Removing old code before new is proven
Do:
-
Small, reversible steps
-
Test rollback procedures
-
Batch large data migrations
-
Keep old paths until new verified