E-Commerce Platform: Strangler Pattern Migration
28-month Symfony frontend enabling zero-downtime migration from Magento 1 to Sylius through sophisticated multi-API orchestration and progressive enhancement.
The Challenge
A commodities e-commerce platform faced a critical inflexion point. Their Magento 1 platform had reached end-of-life, requiring migration to Sylius whilst maintaining uninterrupted operations. The challenge wasn't just technical migration. It was business continuity during a multi-year transformation.
The platform coordinated sevendistinct backend APIs, each with different capabilities. Magento handled catalogue and customer authentication. A backend trading system managed order processing. A financial services system handled account management and validation. Sylius represented the future platform. Separate APIs delivered real-time pricing data, admin configuration, and customer portal features. Each system used different authentication (session cookies, API keys, JWT tokens), data formats (JSON, XML, proprietary), and availability patterns.
Show why this became intractableHide why this became intractable
Replacing multiple integrated systems simultaneously presented unacceptable risk. A single deployment replacing all seven API integrations created failure scenarios where any backend issue would take down the entire platform. Recovery would require rolling back everything, not just the failing component. Testing all possible interaction paths between systems was practically impossible.
The business constraint was absolute: zero downtime tolerance. E-commerce platforms processing real-time transactions can't pause operations. Market dynamics mean customers expect to transact at any moment. Brief interruptions during peak hours lose revenue and erode customer trust.
Session management added complexity. Magento maintained server-side session state through cookies (frontend and frontend_cid), tracking cart contents, checkout progress, and customer authentication. The Symfony frontend needed access to this state without duplicating it or creating synchronisation issues.
The solution required an architecture enabling gradual migration whilst maintaining full operational capability throughout a multi-year transformation. Each backend system needed to be replaceable independently, testable alongside legacy systems, and capable of instant rollback if issues emerged.
The critical business requirement wasn't just migration. It was creating a migration enabler. The frontend needed to abstract backend dependencies so completely that individual systems could be replaced independently without frontend changes. This architectural approach would allow gradual backend migration, reduce deployment risk, enable independent testing of replacement systems alongside legacy, provide immediate rollback capability, and let feature development continue during migration.
The Strangler Pattern Solution
The architecture implements the Symfony strangler pattern through complete interface abstraction. Named after strangler fig trees that germinate in tree canopies and eventually replace their hosts, this pattern allows new systems to grow around legacy systems, gradually assuming responsibilities until the legacy system can be removed.
The core architectural decision was interface-driven abstraction. Every backend operation exposes an interface defining its contract, with concrete implementations containing the actual API calls. Controllers depend only on interfaces. Backends swap through dependency injection configuration changes rather than code modifications.
The authentication layer shows the strangler pattern in action. The custom Symfony Security authenticator bridges modern authentication patterns with Magento's legacy session cookies. The interface abstraction means we can switch to Sylius authentication without touching the authenticator itself.
Token caching implements sophisticated performance optimisation. Sylius JWT tokens expire after 60 minutes, so the architecture caches them for 59minutes, refreshing before expiry whilst maintaining security. Authentication overhead drops from potentially hundreds of requests per session to a single request per hour. An order-of-magnitude performance improvement for distributed architectures.
Cross-system context preservation proved essential for background job processing. When a customer triggers an action dispatched to Symfony Messenger for background processing, the message handler runs in a different context without access to the original request's session, cookies, or environment details. Custom Messenger middleware captures context during dispatch (Magento URL, currency settings) and restores it during consumption, ensuring background processes operate with the same configuration as the original request.
Progressive Enhancement with Hotwire
The frontend architecture chose Hotwired Stimulus and Symfony UX over React/Vue frameworks, prioritising progressive enhancement and server-side business logic authority. This decision proved critical for accessibility and maintainability.
Trading operations demanded sub-second price updates, instant order confirmation, and live availability checking. The architecture achieves this through 111Live Components for reactive server-side updates, whilst 67Stimulus controllers manage client-side interactivity. The crucial distinction? JavaScript enhances base functionality rather than providing it.
Show why progressive enhancement mattersHide why progressive enhancement matters
The site functions completely without JavaScript. Forms submit through standard HTTP POST, price data displays as tables, and checkout works through full page loads. Analytics show approximately 2% of purchases occur without JavaScript. These customers complete transactions successfully because base functionality doesn't depend on client-side code.
This approach delivers multiple benefits. Search engine indexing works perfectly (server-side rendering). Screen readers access content reliably. Network issues blocking JavaScript don't break the site. Testing is simplified (base functionality testable through integration tests without browser automation).
JavaScript adds real-time updates, timer-based refresh, enhanced dropdowns, and smooth transitions. But these are enhancements, not requirements.
Real-time trading demanded sophisticated state management. The timer-based price refresh shows thoughtful UX design addressing business risk. Commodity prices fluctuate constantly. Allowing transaction submission with stale prices risks financial loss for either the customer or the business. The solution? A visible countdown timer showing exactly when prices will refresh, building trust through transparency whilst guaranteeing data freshness.
Live Components maintain server-side authority whilst providing reactive experiences. When prices update or orders complete, components re-render on the server with fresh data. Calculations occur server-side using authoritative business logic. Only rendered HTML is sent to the client. This eliminates client-side state management complexity and keeps business rules centralised without duplication between frontend and backend.
Event-driven architecture keeps components loosely coupled. When a trade completes, the modal dispatches a custom event (order:created). Multiple components listen independently. The trade modal clears its state. The holdings list refreshes through its Live Component. The balance display updates. The order history fetches new data. None of these components couple to each other. They couple only to the shared event contract.
Bundle size optimisation achieved significant results through manual tree-shaking configuration. Chart.js claims tree-shaking support, but importing the library bundled the entire 500KB+ package. Dynamic plugin registration prevented standard tree-shaking from working. The webpack configuration manually declares Chart.js modules as having no side effects, forcing dead code elimination. Individual plugins are imported as separate modules rather than through the Chart.js bundle. The final result: under 500KBtotal JavaScript (Chart.js contribution reduced from 500KB to approximately 120KB), compared to multi-megabyte React applications.
NOT COOKIE CUTTER Decisions
This project made several decisions diverging from standard approaches, each justified by project constraints and business requirements.
Interface Markers for Cross-Cutting Concerns
Trading operations require availability checking before execution (preventing customers from attempting trades during maintenance windows or system failures). But implementing explicit checks in every controller method creates duplication and risks inconsistent enforcement where forgotten checks create vulnerabilities. The solution? Empty marker interfaces, enabling declarative behaviour with zero boilerplate.
Live Components Over Custom API Endpoints
Traditional reactive frontends implement custom API endpoints returning JSON, with frontend JavaScript managing state updates. This duplicates logic between API response formatting and HTML rendering. It creates state synchronisation complexity. It risks divergence between client and server rendering.
Symfony UX Live Components eliminate these issues by rendering entirely server-side. Components receive events. They execute business logic server-side using the same services as traditional requests. They render templates using the same Twig templates. They return HTML. The framework handles communication transparently.
Show why this pattern succeedsHide why this pattern succeeds
Server-side rendering maintains a single source of truth for business logic. No duplication between API formatting and HTML rendering, no client-side state management complexity, and no risk of client calculations diverging from server calculations.
Progressive enhancement works naturally. Components degrade gracefully to standard HTTP forms. The same template renders both initial page loads and reactive updates. Development teams with stronger server-side expertise leverage existing knowledge rather than learning complex frontend frameworks.
This approach suits applications where server-side rendering is already implemented and should be leveraged, business logic complexity makes duplication risky, or development teams have stronger server-side than frontend framework expertise.
Visible Timer vs Silent Polling
Trading interfaces typically implement invisible background price polling with no customer visibility into data freshness. This creates uncertainty. Customers cannot assess whether prices are current. They hesitate to trade due to uncertainty about price currency. They develop false confidence in stale data.
The visible countdown timer sets clear expectations (you have exactly this much time before refresh). It builds trust through transparency (we're showing you when data will refresh). It maintains data freshness (prices remain current within this interval). The automatic cancellation mechanism prevents stale submissions even if customers click submit just before expiry. Zero stale trades processed.
This pattern transfers to any time-sensitive application where stale data presents business risk: stock trading platforms requiring current market prices, auction systems with time-limited bids, limited inventory booking where availability changes rapidly, and currency exchange applications with fluctuating rates.
Measurable Outcomes
The strangler pattern implementation delivers its intended capability. Backend systems can be replaced incrementally without frontend modifications. The architecture already proves this with Sylius integration prepared but not yet activated.
Zero-Downtime Migration
- • 28 months of development with zero deployment-related trading outages
- • Trading operations continued uninterrupted throughout migration
- • Sylius integration ready to activate via configuration changes only
- • Gradual feature migration occurred transparently to customers
Multi-API Orchestration
- • Seven backend APIs coordinated through unified interface layer
- • Authentication overhead reduced from hundreds of requests to one per hour
- • 240 API integration files (110 interfaces, 110 implementations)
- • 49 Magento endpoint wrappers providing complete legacy coverage
Progressive Enhancement
- • Complete functionality without JavaScript (verified 2% purchase rate)
- • JavaScript bundle under 500KB (Chart.js alone reduced 500KB to 120KB)
- • 111 Live Components + 67 Stimulus controllers for reactive experiences
- • Server-side rendering maintains SEO and accessibility
Code Quality
- • PHPStan strict mode passes on all 921 PHP files (55,345 lines)
- • 223 PHPUnit tests + 7 Jest suites covering critical controllers
- • 1,474 commits over 28 months across 5 developers
- • PHP 8.1 with strict typing, readonly properties, and enums throughout
This architecture is a blueprint for organisations facing legacy migration where zero-downtime operation is required.
The key transferable insights? Interface-driven abstraction lets you replace components independently. Parallel operation lets new and old systems run simultaneously. Incremental deployment reduces risk through gradual cutover. Configuration-based switching means instant rollback if issues emerge.
Key Learnings
Strangler Pattern Requires Complete Abstraction
Partial abstraction fails catastrophically. Every backend operation must expose an interface (not just "important" ones). Cookie management must be centralised (scattered cookie handling creates debugging nightmares). Authentication must work through dependency injection (hardcoded authentication calls can't be replaced). Any direct coupling to legacy systems creates migration blockers requiring code changes when you need configuration changes. The 240 API integration files (110 interfaces, 110 implementations) show the discipline required.
Progressive Enhancement Isn't Just Accessibility
Server-side authority prevents duplicate business logic (no divergence between client and server calculations). It simplifies testing (base functionality testable without browser automation requiring Playwright or Cypress). It reduces JavaScript complexity (no complex state management libraries needed). It maintains SEO and accessibility (search engines see content, screen readers work perfectly). The 2% of purchases without JavaScript validates the approach's practical benefits, not theoretical ones.
Context Preservation in Async Operations
Background jobs lose request context automatically (session, cookies, environment details vanish in worker processes). Explicit parameter passing for context creates bloated message payloads and risks forgotten parameters creating subtle bugs. Custom Messenger middleware capturing context during dispatch and restoring during consumption proved essential for maintaining session continuity across Magento's cookie-based sessions and multi-currency pricing calculations requiring environment-specific configuration.
Visible State Builds Trust
The visible countdown timer for price refresh sets clear customer expectations (you have exactly 30 seconds before prices refresh). It builds confidence through transparency. Silent background updates create uncertainty where customers don't know if prices are current or stale. Transparency about data freshness proved more valuable than hiding implementation details. This isn't just good UX. It's risk reduction preventing stale price submissions.
Token Caching is Performance Critical
Caching JWT tokens for 59 minutes (one minute before 60-minute expiry) reduced authentication overhead from hundreds of requests per session to one per hour. This represents order-of-magnitude performance improvement for distributed architectures coordinating multiple authenticated APIs. The one-minute buffer ensures tokens refresh before expiry whilst maintaining security (tokens never cache after expiry).
Interface Markers Enable Declarative Behaviour
Empty marker interfaces using PHP's type system for semantics proved cleaner than attribute-based approaches or explicit method calls. Controllers declare their nature through interface implementation (TradingInterface). Event subscribers detect through instanceof checks (type-safe). The pattern delivers zero boilerplate (just the interface declaration). This approach suits any cross-cutting concern enforced across multiple controllers: feature flags, logging, rate limiting, permission checking.
Facing a Legacy Platform Migration?
We specialise in strangler pattern implementations enabling zero-downtime migrations from legacy platforms. Whether you're migrating e-commerce, SaaS, or enterprise applications, our architecture patterns reduce risk whilst maintaining business continuity.
Who This Helps
Platform migration strategies for different audiences
Ready to eliminate your technical debt?
Transform unmaintainable legacy code into a clean, modern codebase that your team can confidently build upon.