1,500 Legacy PHP Files into 15 Microservices — All the Way to Production
I wrote about legacy refactoring in an earlier post, and it’s time for the follow-up on how far that project got. Short version: it went into production.
What we built
A kiosk plus franchise remote-management SaaS for unmanned businesses — study cafés, reading rooms, car washes, golf driving ranges. One platform covers 13 industries. It started as a PHP monolith of 1,500 files. Today:
- 15 Spring Boot microservices + 3 shared modules (Eureka + Gateway)
- 1,432 Java sources, 139 controllers, 135 JPA entities
- 457 Flyway migrations (the entire history of the schema’s evolution, preserved)
- An Electron kiosk (271 TS/TSX files) + a Next.js admin
- The whole AWS infrastructure codified in 36 Terraform files
Getting 13 industries into one codebase
The legacy system’s biggest problem was per-industry if-else. Car-wash logic and study-café logic branched inside the same function, and every new industry added another branch. I replaced that with a plugin architecture.
I defined an IndustryPlugin interface and assembled per-industry plugins through Spring auto-configuration. Plugins declare 23 feature flags and 22 product types; the core knows nothing about industries. Adding a new industry means implementing one plugin class. In practice, onboarding a new industry dropped to a matter of days.
The hardware world
A kiosk doesn’t end at software. I built a hardware driver layer directly into the Electron main process: card dispensers, cash hoppers, coin hoppers, RF card readers, barcode scanners, relays, receipt printers (ESC/POS), and the VAN payment module. Offline mode, heartbeats, remote commands, and split auto-update channels (dev/prod) are all handled in that layer too.
Barrier-free — a deadline written by law
From 2026, accessibility requirements for unmanned information terminals phase in. I implemented high contrast, color inversion, font scaling, captions, voice guidance, and visual alerts directly in the kiosk runtime. The accessibility code alone is over 40KB.
Honestly, it started as “regulatory compliance,” and building it changed my mind. An unmanned store has no staff. If the kiosk doesn’t work, that customer simply leaves. Accessibility turned out not to be a feature of an unmanned business but a precondition for it.
Operating is a different sport
Building and operating really are different things. The operational safeguards we put in place on this project:
- RDS delete protection + a guard on
terraform apply - An explicit
CONFIRM_PROD=YESconfirmation on deploy - A rollback runbook for the payment service (written before the incident)
- Saga distributed transactions + duplicate-payment protection — so the same payment request arriving twice is processed once (in a distributed system, “exactly once” is never free)
A system where real payments flow and real revenue moves creates a kind of tension that has nothing to do with code quality. I like that tension. It’s the point where code meets reality.