Strangler Fig Concept
Named After Nature
The strangler fig tree grows around a host tree, gradually replacing it. In software, we build new systems alongside legacy ones, routing traffic incrementally until the old system can be removed.
Magento 2 Application
Identify Strangling Points
// Legacy monolithic service
class LegacyOrderService
{
public function createOrder($data) { /* 500 lines */ }
public function processPayment($data) { /* 300 lines */ }
public function sendConfirmation($data) { /* 100 lines */ }
public function updateInventory($data) { /* 200 lines */ }
}
// Strangling points:
// 1. Order creation → New OrderService
// 2. Payment processing → New PaymentService
// 3. Notifications → New NotificationService
// 4. Inventory → New InventoryService
The Four Phases
1. Identify seams (boundaries between concerns)
2. Build new alongside old
3. Route traffic gradually
4. Remove old when fully replaced
Timeline example:
Month 1-2: Identify seams, design new services
Month 3-4: Build OrderService, route 10% traffic
Month 5-6: Build PaymentService, route 25% traffic
Month 7-8: Build remaining services, route 50% traffic
Month 9-10: Route 100% traffic, remove legacy
Benefits for Magento
1. No big-bang rewrite risk
2. Continuous delivery of value
3. Easy rollback at each step
4. Team learns incrementally
5. Business operations continue
6. Costs spread over time
Traffic Routing
Routing Strategies
Plugin-Based Routing
<!-- di.xml - Route to new service gradually -->
<config>
<type name="Magento\Sales\Model\Order">
<plugin name="route_to_new_order_service"
type="Vendor\Migration\Plugin\OrderRoutingPlugin"
sortOrder="10"/>
</type>
</config>
// Plugin implementation
namespace Vendor\Migration\Plugin;
class OrderRoutingPlugin
{
private int $rolloutPercentage;
public function __construct(
private LegacyOrderService $legacyService,
private NewOrderService $newService,
private ConfigInterface $config
) {
$this->rolloutPercentage = $this->config->get('migration/order_rollout_percentage');
}
public function beforeCreateOrder(
\Magento\Sales\Model\Order $subject,
array $data
): array {
// Route based on percentage
if (rand(1, 100) <= $this->rolloutPercentage) {
// Use new service
$result = $this->newService->createOrder($data);
// Log for monitoring
$this->logger->info('New service used for order');
return [$data];
}
// Fall back to legacy
$this->logger->info('Legacy service used for order');
return [$data];
}
}
Feature Flag Routing
// Feature flag based routing
public function createOrder(array $data): OrderInterface
{
if ($this->featureFlag isEnabled('new_order_flow')) {
return $this->newOrderService->create($data);
}
return $this->legacyOrderService->create($data);
}
User-Based Routing
// Route specific customers to new system
public function createOrder(array $data): OrderInterface
{
$customerId = $data['customer_id'] ?? null;
// Beta customers first
if (in_array($customerId, $this->betaCustomerIds)) {
return $this->newOrderService->create($data);
}
// Customer segments
if ($this->isHighValueCustomer($customerId)) {
return $this->newOrderService->create($data);
}
return $this->legacyOrderService->create($data);
}
Geographic Routing
// Route by region
public function createOrder(array $data): OrderInterface
{
$region = $data['shipping_address']['region_code'];
if (in_array($region, ['US', 'CA', 'UK'])) {
// New regions use new system
return $this->newOrderService->create($data);
}
// Other regions use legacy
return $this->legacyOrderService->create($data);
}
Monitoring Routing
// Track which system is being used
public function logRoutingDecision(string $service, array $context): void
{
$this->statsd->increment("migration.routing.{$service}");
$this->logger->info('Routing decision', [
'service' => $service,
'order_id' => $context['order_id'] ?? null,
'customer_id' => $context['customer_id'] ?? null,
'percentage' => $this->rolloutPercentage,
]);
}
Old System Retirement
Retirement Criteria
Readiness Checklist
Code:
- [ ] 100% traffic routed to new system
- [ ] No errors from legacy paths
- [ ] All tests passing without legacy code
- [ ] PHPStan/PSalm clean
Operations:
- [ ] Monitoring shows no legacy usage
- [ ] No support tickets related to legacy
- [ ] Team trained on new system
- [ ] Documentation updated
Business:
- [ ] Stakeholder sign-off
- [ ] No pending features on legacy
- [ ] Performance meets or exceeds legacy
- [ ] Cost savings documented
Phased Retirement
Phase 1: Disable legacy entry points
- Remove legacy routes
- Disable legacy API endpoints
- Keep code for reference
Phase 2: Remove dead code
- Delete unused legacy classes
- Remove legacy configuration
- Clean up database tables
Phase 3: Remove legacy infrastructure
- Remove legacy servers/containers
- Archive legacy logs
- Update architecture diagrams
Phase 4: Document learnings
- Write migration post-mortem
- Update runbooks
- Share lessons with team
Database Cleanup
-- After legacy code removed
-- Archive legacy tables
RENAME TABLE legacy_orders TO legacy_orders_archive;
-- Remove legacy columns
ALTER TABLE orders DROP COLUMN legacy_status;
-- Clean up legacy data
DELETE FROM legacy_data WHERE created_at < '2024-01-01';
Rollback After Retirement
If legacy is removed but issues found:
1. Re-enable legacy code from version control
2. Restore legacy database tables from backup
3. Route traffic back to legacy
4. Fix issues in new system
5. Re-attempt retirement
Max rollback window: 30 days after retirement
Success Metrics
Migration is successful when:
- 100% traffic on new system for 30+ days
- Error rate same or lower than legacy
- Performance same or better
- No critical issues for 2 weeks
- Team confident in new system
- Legacy code fully removed
Dual Running
Running Old and New Together
Data Synchronization
// Keep data in sync during migration
public function syncOrderData(int $orderId): void
{
// Write to both systems
$legacyOrder = $this->legacyOrderRepository->get($orderId);
$newOrder = $this->convertToNewFormat($legacyOrder);
$this->newOrderRepository->save($newOrder);
// Log sync for monitoring
$this->logger->info('Order synced', ['order_id' => $orderId]);
}
Consistency Checks
// Verify data consistency between systems
public function verifyOrderConsistency(int $orderId): bool
{
$legacy = $this->legacyOrderRepository->get($orderId);
$new = $this->newOrderRepository->get($orderId);
$diffs = [];
if ($legacy->getGrandTotal() !== $new->getGrandTotal()) {
$diffs[] = 'total_mismatch';
}
if ($legacy->getStatus() !== $new->getStatus()) {
$diffs[] = 'status_mismatch';
}
if (!empty($diffs)) {
$this->alertService->send('Data inconsistency', $diffs);
return false;
}
return true;
}
Cost of Dual Running
Infrastructure:
- Double server capacity
- Additional database storage
- Duplicate cache layers
- Additional monitoring
Operations:
- Team supports both systems
- More complex debugging
- Additional deployment steps
- Extended testing requirements
Minimize duration:
- Plan aggressive but safe timeline
- Automate synchronization
- Monitor closely
- Fix issues quickly
Transition Checklist
Daily during dual running:
- [ ] Verify data sync is working
- [ ] Check error rates on both systems
- [ ] Monitor performance metrics
- [ ] Review routing percentages
- [ ] Address any inconsistencies
Weekly:
- [ ] Review migration progress
- [ ] Adjust rollout percentage
- [ ] Update stakeholders
- [ ] Plan next phase
Monthly:
- [ ] Comprehensive comparison
- [ ] Cost analysis
- [ ] Timeline review
- [ ] Risk assessment update
Practice Problems
Implement a strangler fig migration for the order processing module, including traffic routing and monitoring.
Create a plan to retire the legacy order processing system after successful migration.
Quiz
1. What is the first step in strangler fig migration?
2. How is traffic routed between old and new systems?
3. When can the old system be retired?
4. What is dual running?
Flashcards
Question
What is strangler fig pattern?
Click to reveal answer
Answer
Incrementally replace legacy by building new alongside old, routing traffic gradually
Question
What are seams?
Click to reveal answer
Answer
Boundaries between concerns where code can be split into separate services
Question
How to route traffic?
Click to reveal answer
Answer
Using plugins, feature flags, user-based rules, or geographic routing
Question
When to retire old system?
Click to reveal answer
Answer
After 100% traffic for 30+ days, no errors, performance meets baseline
Question
What is dual running cost?
Click to reveal answer
Answer
Double infrastructure, additional operations overhead, extended testing requirements
Revision Notes
Key Takeaways
- 1. Strangler fig = 4 phases: identify seams, build new, route traffic, remove old
- 2. Use plugins and feature flags for gradual traffic routing
- 3. Monitor routing decisions and data consistency during dual running
- 4. Retirement criteria: 100% traffic 30+ days, no errors, stakeholder sign-off
- 5. Minimize dual running duration to reduce costs
- 6. Always have rollback plan even after retirement (30-day window)
Interview Tips
- • Explain the strangler fig pattern and why it's safer than big-bang rewrite
- • How do you route traffic between old and new systems?
- • What are the costs of dual running?
- • How do you know when to retire the old system?
- • Describe a data synchronization challenge during migration
Cheat Sheet
Strangler Fig Pattern Cheat Sheet
4 Phases:
- Identify seams (boundaries)
- Build new alongside old
- Route traffic gradually
- Remove old when fully replaced
Routing Methods:
- Plugin-based (percentage)
- Feature flags (boolean)
- User-based (beta customers)
- Geographic (by region)
Retirement Criteria:
- 100% traffic 30+ days
- No errors from legacy
- Performance meets baseline
- Stakeholder sign-off
Dual Running Costs:
- 2x infrastructure
- Additional operations
- Extended testing