How do I peel the payment module off a PHP monolith with Strangler Fig?
Question
I have a large PHP monolith. I want to peel off the "Payment" module and turn it into an independent Go microservice. I want to route traffic gradually (5%, 20%, 100%) to the new service without users or the existing code noticing. How do I set this up at the API Gateway (Envoy/Nginx) level? How do I keep consistency while splitting the databases?
Answer
Short answer: put a facade/gateway (Nginx/Envoy) in front, shift traffic by weight (5/20/100), keep the monolith as fallback. But the hard part isn’t routing — it’s the data.
Short answer
The logic of Strangler Fig is this: you don’t kill the old module at once, you wrap it and slowly shift traffic to the new service. The moment the module comes off, a message contract is born between the two sides; I covered how to set that contract up in the question about the message schema between Laravel and Go.
Why
-
Routing is easy, data is hard. Changing a weight at the gateway is one line of config; separating shared tables can take months. Plan against the second one.
-
With money on the line there’s no “hope it’s right”. The only way to validate the new service before it handles real money is to compare its outputs against the monolith’s.
-
Extracting a module without a clean boundary accelerates the disaster. If the payment module is tangled into the monolith’s tables, Strangler Fig doesn’t untangle it — it just adds traffic on top.
What to do
-
Set up weighted routing at the gateway. Keep Nginx/Envoy in front, send payment traffic to the new Go service by weight
5% → 20% → 100%, the rest to the monolith. Make the rollback one line of config. -
Run it in SHADOW first. Mirror traffic, compare outputs against the monolith’s, take no real action. If the numbers match, take it live.
-
Never share tables. Give the payment service its own store; put an anti-corruption layer in between so the monolith’s model doesn’t leak into the new service.
-
Keep consistency with outbox + events. Don’t reach for distributed transactions; write the change to your own DB, drop an outbox record in the same transaction, publish it as an event. Migrate data with dual-write + backfill, then reconcile, then cut over.
-
Fix the seam first. If the boundary isn’t already clean, clean it before you extract.
Bottom line: I’d set up weighted routing + monolith fallback at the gateway, validate the new Go service in shadow on mirrored traffic (not real money) first, then roll it out gradually. Don’t forget the real work is the data split, not the routing: no shared tables, an anti-corruption layer, consistency via outbox/events. I unpack the architectural depth on sade.dev — don’t try to peel off a module whose boundary isn’t clean.
Related Reading
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.