Feature-by-Feature Migration
Decomposing the Monolith
Feature Map
Current Monolith:
┌─────────────────────────────────────â”
│ Legacy Magento │
├─────────┬─────────┬─────────┬──────┤
│ Catalog │ Orders │ Payments│ Users │
│ - Products│ - Create│ - Stripe│ - Auth│
│ - Search │ - Status│ - PayPal│ - ACL │
│ - Rules │ - Refund│ - Invoice│ - API │
└─────────┴─────────┴─────────┴──────┘
Target: Migrate features independently
Migration Order (by dependency):
1. User Authentication (no dependencies)
2. Product Catalog (depends on users)
3. Cart (depends on catalog)
4. Orders (depends on cart, payments)
5. Payments (depends on orders)
Migration Priority Matrix
Feature | Business Value | Complexity | Dependencies | Priority
---------------|---------------|------------|-------------|----------
User Auth | High | Low | None | P1
Product Catalog| High | Medium | Users | P1
Search | Medium | Medium | Catalog | P2
Cart | High | Medium | Catalog | P1
Checkout | High | High | Cart, Pay | P2
Orders | High | High | Checkout | P3
Payments | High | High | Orders | P3
Admin Panel | Medium | Medium | All | P4
Feature Migration Steps
For each feature:
1. Define interface (API contract)
2. Build new implementation
3. Write tests for both old and new
4. Implement data sync
5. Route traffic gradually
6. Verify consistency
7. Remove old code
8. Update documentation
Estimation per Feature
$featureEstimates = [
'user_auth' => [
'new_code' => 40, // hours
'tests' => 16,
'data_sync' => 8,
'integration' => 16,
'total' => 80
],
'product_catalog' => [
'new_code' => 80,
'tests' => 32,
'data_sync' => 24,
'integration' => 24,
'total' => 160
],
];
Data Synchronization
Sync Strategies
Real-Time Sync
// Event-based synchronization
class OrderSyncService
{
public function __construct(
private EventManager $eventManager,
private LegacyOrderRepository $legacyRepo,
private NewOrderRepository $newRepo
) {
$this->eventManager->dispatch('order_save_after', [
'order' => $order,
'sync_callback' => [$this, 'syncOrder']
]);
}
public function syncOrder(OrderInterface $order): void
{
$newOrder = $this->convertOrder($order);
$this->newRepo->save($newOrder);
}
}
Batch Sync
// Nightly batch synchronization
public function syncOrdersBatch(int $batchSize = 1000): void
{
$lastId = $this->getLastSyncedId();
do {
$orders = $this->legacyRepo->getOrdersAfter($lastId, $batchSize);
foreach ($orders as $order) {
$this->syncOrder($order);
$lastId = $order->getId();
}
$this->updateLastSyncedId($lastId);
} while (count($orders) === $batchSize);
}
Conflict Resolution
// Handle sync conflicts
public function resolveConflict(
OrderInterface $legacy,
OrderInterface $new
): OrderInterface {
// Strategy 1: Last write wins
if ($legacy->getUpdatedAt() > $new->getUpdatedAt()) {
return $this->convertToNew($legacy);
}
// Strategy 2: Legacy wins during migration
if ($this->isLegacyPrimary()) {
return $this->convertToNew($legacy);
}
// Strategy 3: Manual resolution
$this->flagForReview($legacy, $new);
return $new; // Default to new
}
Sync Monitoring
// Monitor sync health
public function getSyncStatus(): array
{
return [
'legacy_count' => $this->legacyRepo->count(),
'new_count' => $this->newRepo->count(),
'synced_count' => $this->getSyncedCount(),
'pending_count' => $this->getPendingCount(),
'error_count' => $this->getErrorCount(),
'last_sync' => $this->getLastSyncTime(),
'sync_lag' => $this->getSyncLag(),
];
}
Dual-Write Patterns
Writing to Both Systems
Synchronous Dual-Write
// Write to both systems atomically
public function saveOrder(OrderInterface $order): void
{
$transaction = $this->transactionFactory->create();
try {
// Write to new system
$this->newOrderRepo->save($order);
// Write to legacy system
$legacyOrder = $this->convertToLegacy($order);
$this->legacyOrderRepo->save($legacyOrder);
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}
}
Asynchronous Dual-Write
// Write to primary, queue secondary
public function saveOrder(OrderInterface $order): void
{
// Write to primary (new system)
$this->newOrderRepo->save($order);
// Queue for legacy sync
$this->messageQueue->publish('order.sync', [
'order_id' => $order->getId(),
'action' => 'save',
'timestamp' => time()
]);
}
// Consumer process
public function processSyncMessage(array $message): void
{
$order = $this->newOrderRepo->get($message['order_id']);
$legacyOrder = $this->convertToLegacy($order);
$this->legacyOrderRepo->save($legacyOrder);
}
Switching Primary System
// Gradually switch which system is primary
public function saveOrder(OrderInterface $order): void
{
if ($this->isNewSystemPrimary()) {
// New is primary, write to legacy async
$this->newOrderRepo->save($order);
$this->queueLegacySync($order);
} else {
// Legacy is primary, write to new async
$this->legacyOrderRepo->save($order);
$this->queueNewSync($order);
}
}
Error Handling
// Handle dual-write failures
public function saveOrderWithRetry(OrderInterface $order, int $maxRetries = 3): void
{
for ($i = 0; $i < $maxRetries; $i++) {
try {
$this->saveOrder($order);
return;
} catch (\Exception $e) {
$this->logger->warning('Dual-write failed', [
'attempt' => $i + 1,
'error' => $e->getMessage()
]);
if ($i === $maxRetries - 1) {
$this->flagForManualReview($order, $e);
throw $e;
}
sleep(pow(2, $i)); // Exponential backoff
}
}
}
Data Validation
Consistency Verification
Automated Validation
// Verify data consistency between systems
public function validateOrderConsistency(int $orderId): ValidationResult
{
$legacy = $this->legacyRepo->get($orderId);
$new = $this->newRepo->get($orderId);
$diffs = [];
// Compare fields
$fields = ['grand_total', 'status', 'customer_email', 'shipping_method'];
foreach ($fields as $field) {
$legacyValue = $legacy->getData($field);
$newValue = $new->getData($field);
if ($legacyValue != $newValue) {
$diffs[] = [
'field' => $field,
'legacy' => $legacyValue,
'new' => $newValue
];
}
}
return new ValidationResult(
count($diffs) === 0,
$diffs
);
}
Bulk Validation
// Validate all synced orders
public function validateAllSynced(int $limit = 10000): array
{
$results = [
'total' => 0,
'consistent' => 0,
'inconsistent' => 0,
'missing_legacy' => 0,
'missing_new' => 0
];
$orderIds = $this->getSyncedOrderIds($limit);
foreach ($orderIds as $orderId) {
$results['total']++;
$legacy = $this->legacyRepo->get($orderId);
$new = $this->newRepo->get($orderId);
if (!$legacy) {
$results['missing_legacy']++;
continue;
}
if (!$new) {
$results['missing_new']++;
continue;
}
if ($this->validateOrderConsistency($orderId)->isConsistent()) {
$results['consistent']++;
} else {
$results['inconsistent']++
}
}
return $results;
}
Reconciliation Reports
// Generate reconciliation report
public function generateReport(): string
{
$report = "Data Reconciliation Report\n";
$report .= str_repeat('=', 50) . "\n\n";
$results = $this->validateAllSynced();
$report .= "Total Orders Validated: {$results['total']}\n";
$report .= "Consistent: {$results['consistent']}\n";
$report .= "Inconsistent: {$results['inconsistent']}\n";
$report .= "Missing in Legacy: {$results['missing_legacy']}\n";
$report .= "Missing in New: {$results['missing_new']}\n\n";
$consistencyRate = ($results['consistent'] / $results['total']) * 100;
$report .= sprintf("Consistency Rate: %.2f%%\n", $consistencyRate);
if ($results['inconsistent'] > 0) {
$report .= "\nInconsistent Orders:\n";
$inconsistent = $this->getInconsistentOrders();
foreach ($inconsistent as $order) {
$report .= "- Order #{$order['id']}: {$order['diff_count']} differences\n";
}
}
return $report;
}
Practice Problems
Create a feature-by-feature migration plan for migrating the product catalog from legacy to new system.
Implement real-time data synchronization between legacy and new order systems with conflict resolution.
Quiz
1. What should be migrated first in feature-by-feature approach?
2. What is dual-write?
3. How to handle sync conflicts?
4. What does data reconciliation verify?
Flashcards
Question
What is feature-by-feature migration?
Click to reveal answer
Answer
Migrating one feature at a time, starting with those having no dependencies
Question
What is dual-write?
Click to reveal answer
Answer
Writing to both old and new systems during migration transition
Question
How to handle sync conflicts?
Click to reveal answer
Answer
Last write wins, primary system preference, or manual review
Question
What is data reconciliation?
Click to reveal answer
Answer
Verifying data consistency between legacy and new systems
Question
What is sync lag?
Click to reveal answer
Answer
The delay between writing to primary system and it appearing in secondary system
Revision Notes
Key Takeaways
- 1. Migrate features with no dependencies first to build foundation
- 2. Dual-write keeps both systems in sync during transition
- 3. Real-time sync uses events; batch sync runs on schedule
- 4. Conflict resolution strategies: last write wins, primary preference, manual review
- 5. Data reconciliation must be automated and run continuously
- 6. Monitor sync lag and error rates during migration
Interview Tips
- • How do you decide which features to migrate first?
- • Explain dual-write pattern and its trade-offs
- • How do you handle data sync conflicts?
- • What metrics do you track during incremental migration?
- • How do you validate data consistency between systems?
Cheat Sheet
Incremental Migration Cheat Sheet
Migration Order:
- No dependencies first
- Build foundation
- Layer dependent features
Sync Strategies:
- Real-time: event-based
- Batch: scheduled job
- Queue: asynchronous
Dual-Write:
- Synchronous: atomic both
- Async: primary first, queue secondary
- Monitor: sync lag, errors
Validation:
- Automated field comparison
- Bulk reconciliation
- Reconciliation reports
- Continuous monitoring