Legacy Payment Gateway to Modern Microservices Migration
Migrated 15-year-old payment processing monolith to event-driven microservices without transaction loss, processing $50M daily while reducing incidents by 80%.
[Overview] System Overview
A payment service provider processing $50M daily was trapped by their own success. The 15-year-old Java monolith was unmodifiable-every change risked transaction failures.
We executed a strangler fig migration: building new microservices around the legacy core, gradually routing traffic to modern components, and eventually decommissioning the monolith.
The migration completed over 18 months without a single transaction loss. Incidents dropped 80%. New feature velocity increased 5x.
The new architecture is event-driven, using Kafka for transaction orchestration and saga patterns for distributed consistency.
[Context] Situation before
The legacy system was a 2M+ line Java EE application, running on WebSphere, with business logic spanning 15 years of requirements.
##### The Fear Factor
No engineer would voluntarily modify the payment orchestration code. The last significant change caused a 3-hour outage.
The original architects had left years ago. Current engineers understood maybe 40% of the codebase.
Automated tests covered 8% of code. Manual regression testing took 3 weeks per release.
##### Business Pressure
Competitors were launching features in weeks. This system took months for minor changes.
New payment methods (Apple Pay, crypto) required architectural changes impossible in the current system.
Regulatory changes (PSD2, real-time payments) were mandated deadlines the system couldn't meet.
[Diagnosis] The real problem
The obvious problem was legacy code. The tempting solution was a full rewrite.
But a rewrite would take 3+ years and carry extreme risk. The legacy system embodied 15 years of edge cases-discovered through production incidents. A rewrite would rediscover them all.
The real problem was coupling. The monolith did everything: payment routing, fraud detection, settlement, reporting. Decoupling these concerns would allow incremental modernization.
Understanding the root cause changed everything.
[Boundaries] Working constraints
Payment system migration has zero tolerance for data loss.
Constraints are features, not obstacles. They guide design decisions.
[Decisions] Key tradeoffs
Legacy migration strategy balanced risk against velocity.
status="rejected" title="Big Bang Rewrite" description="Complete rebuild, swap when ready" reasoning="3+ year timeline. All-or-nothing risk. Would rediscover every edge case the hard way." /> status="rejected" title="Lift and Shift" description="Containerize existing monolith" reasoning="Doesn't solve the real problem. Same code, different infrastructure. No velocity improvement." /> status="chosen" title="Strangler Fig (gradually replace)" description="Build around, route away, decomission" reasoning="Incremental value delivery. Risk isolated to each component. Rollback is always possible." />
[Design] Architecture design
The strangler fig pattern places a routing layer in front of the monolith. New requests route to new services; unchanged flows continue to legacy.
Event sourcing captures all payment state transitions. The event log is the source of truth-services derive their state from events.
Saga pattern orchestrates multi-step transactions. Each step publishes events; compensating actions handle failures.
"type": "architecture", "title": "Strangler Fig Migration Pattern", "description": "Gradual migration with routing layer", "components": [ "name": "API Gateway", "type": "service", "description": "Traffic router" }, "name": "Legacy Monolith", "type": "service", "description": "Existing system" }, "name": "Payment Service", "type": "service", "description": "New microservice" }, "name": "Fraud Service", "type": "service", "description": "New microservice" }, "name": "Settlement Service", "type": "service", "description": "New microservice" }, "name": "Kafka", "type": "queue", "description": "Event backbone" } ], "connections": [ "from": "API Gateway", "to": "Legacy Monolith", "label": "Old flows", "style": "dashed" }, "from": "API Gateway", "to": "Payment Service", "label": "New flows" }, "from": "Payment Service", "to": "Kafka", "label": "Events" }, "from": "Fraud Service", "to": "Kafka", "label": "Subscribe" }, "from": "Settlement Service", "to": "Kafka", "label": "Subscribe" } ], "boundaries": [ "name": "New Architecture", "contains": [ "Payment Service", "Fraud Service", "Settlement Service", "Kafka" ] } ] }
{/* Diagram: data-flow - Payment Saga Flow */}
{/* ```json
{
"type": "data-flow",
"title": "Payment Saga Flow",
"description": "Distributed transaction with compensation",
"components": [
{
"name": "Payment Request",
"type": "user"
},
{
"name": "Fraud Check",
"type": "service"
},
{
"name": "Bank Auth",
"type": "external"
},
{
"name": "Settlement",
"type": "service"
},
{
"name": "Complete",
"type": "service"
}
],
"connections": [
{
"from": "Payment Request",
"to": "Fraud Check",
"label": "Start saga"
},
{
"from": "Fraud Check",
"to": "Bank Auth",
"label": "If clean"
},
{
"from": "Bank Auth",
"to": "Settlement",
"label": "If approved"
},
{
"from": "Settlement",
"to": "Complete",
"label": "If settled"
}
]
}[Approach] Execution model
Migration proceeded in 18 months, decomposing the monolith capability by capability.
Phase 1 (M1-3): Routing layer + event infrastructure. Shadow mode for all transactions.
Phase 2 (M4-6): Fraud detection migrated. First production traffic to new system.
Phase 3 (M7-12): Core payment processing. Gradual traffic shift 1%→10%→50%→100%.
Phase 4 (M13-18): Settlement, reporting, legacy decommission.
[Results] Measurable outcomes
Zero transaction loss throughout migration. Not one payment dropped.
Incidents: Down 80%. New architecture is observable and debuggable.
Feature velocity: 5x improvement. New payment methods launch in weeks, not months.
On-call burden: Down 70%. Engineers no longer fear the system.
Compliance: PSD2 and real-time payments delivered ahead of deadlines.
title="Key improvements achieved:" items={[ "System handles 10x burst load without degradation", "Delivery latency reduced from seconds to milliseconds", "Zero message loss during traffic spikes", "Operational complexity reduced by 60%" ]} />
[Anti-patterns] What could have gone wrong
A big-bang rewrite would have taken 3+ years, during which competitors would continue advancing.
Regulatory deadlines (PSD2) would have been missed, resulting in fines and market restrictions.
Doing nothing would accelerate technical debt. Eventually, an incident would force emergency action.
variant="yellow" title="" items={[ "Over-engineering the initial solution", "Ignoring operational constraints during design", "Premature optimization without load testing", "Skipping failure mode analysis" ]} />
[Applicability] Where this pattern applies
Strangler fig migration applies to any legacy system too risky for big-bang replacement.
The pattern requires: a routing layer insertion point, decomposable business capabilities, and tolerance for temporary complexity.
The pattern is difficult when: the legacy system has no clear boundaries, or the team lacks experience with distributed systems.
Every system is different. Context always matters.