The monolith problem
The client's checkout had grown for seven years inside a single Rails monolith — pricing, inventory, payments, and shipping all processed in one request and deployed all-or-nothing several times a week.
The breaking point came ahead of last year's Black Friday: a routine change to the loyalty-points module took down the entire checkout for 40 minutes, because there was no boundary between "unrelated" code paths.
Every time we deployed anything, we were nervous. The checkout was impossible to test in isolation because it touched everything.
Migration strategy
We ruled out a full rewrite outright — a 5M-user checkout can't go dark for a "big bang" cutover, and a parallel rebuild would drift out of sync with the live system within weeks. Instead we used the Strangler Fig pattern: put a thin routing layer in front of the monolith and peel services off one at a time while the monolith kept running underneath.
Strangler fig in practice
Every extracted service went through the same four-stage rollout before the old code path was allowed to be deleted:
- Shadow mode: the new service receives a copy of live traffic, never responds to the customer, and its output is diffed against the monolith's
- Canary 5%: a small slice of real traffic is routed to the new service, with alerting on any error-rate divergence
- Dedicated rollout: traffic ramps from 25% to 100% over two weeks, two releases a week, starting with the payment-gateway adapter
- Monolith code freeze: once a service is at 100%, its old code path in the monolith only accepts production-blocking fixes until it is removed
Zero-downtime deployment
Every schema change that crossed the monolith/service boundary followed an expand-contract pattern: add the new column or field, backfill it, migrate both sides to write to it, then remove the old one — never a single migration that both writes and removes in the same deploy, which is what had caused most of the monolith's past downtime.
Lessons learned
The hardest part wasn't the code — it was resisting the urge to redesign each service's API while extracting it. Keeping the new service's contract identical to the monolith's internal call, at least for the first release, is what let us ship weekly instead of quarterly. The migration ran through last year's Black Friday with zero incidents, at 20% higher peak traffic than the year the monolith went down.