How do you break apart a 300,000-line Rails application that is processing real orders in production every hour of the day? That is not a hypothetical — it is the situation engineering teams at growing software companies find themselves in far more often than they would like. The monolith worked fine at $1M ARR. At $10M, every deployment is a team-wide event, database migrations take the whole site down for minutes, and three different squads are stepping on each other's code every sprint. Something has to change. The question is how to do it without the news cycle equivalent of a five-alarm outage.
Why "Rewrite Everything" Always Sounds Better Than It Is
The instinct many teams feel is to start fresh — spin up a new services architecture, migrate data over the weekend, and retire the old codebase by Q2. This plan has a near-100% failure rate at any meaningful scale. The monolith has six years of edge cases baked into it. The new system will rediscover every one of them in production, but now without the institutional knowledge that originally handled them. The approach that actually works is incremental extraction: the strangler-fig pattern.
The Strangler-Fig Pattern Explained Simply
The strangler fig is a tree that grows around its host, gradually taking over until the original tree is no longer needed. Applied to software migration, it means: build new services alongside the existing monolith, route specific traffic to the new services, and slowly shrink the surface area of the old codebase until you can safely shut it down — while it continues to serve production traffic throughout the entire process.
The practical mechanics are:
- Place a reverse proxy or API gateway in front of your monolith
- Extract one bounded context into a new service
- Route that feature's traffic to the new service while everything else continues hitting the monolith
- Validate, stabilize, then repeat for the next context
At no point does the whole application go down for migration. The risk is distributed across dozens of small, reversible steps rather than concentrated into one catastrophic cutover.
Phase One: Understand Your Seams Before You Cut
The most expensive microservices migrations are the ones that drew the service boundaries in the wrong places. Before writing a line of new code, spend time understanding your monolith's natural seams — the boundaries where one capability hands off to another.
How to identify boundaries
- Domain events: Trace the lifecycle of your core domain objects (Order, User, Payment, Shipment, etc.) and note every handoff point — these are candidate boundaries
- Database coupling: Query your database schema for foreign key relationships. Tables with few external foreign keys are easier to extract. Tables that everything touches are the last things you extract, not the first.
- Team ownership: Conway's Law is real. If three squads are regularly editing the same module, that module should probably not be a single service — it will just recreate the coordination problem in a distributed form
- Change frequency: Modules that change frequently and independently of everything else are strong extraction candidates. Modules that barely ever change can stay in the monolith for years without causing harm.
Phase Two: The Proxy Layer — Your Safety Net
Before extracting any service, insert a routing proxy in front of your monolith. This is the most important infrastructure piece in the whole migration. It does several things:
- Enables traffic splitting between old and new implementations at the request level
- Allows you to run new and old systems in parallel (shadow mode) before switching live traffic
- Makes rollback a configuration change rather than a deployment event
- Provides a single place to enforce authentication, rate limiting, and observability
Popular choices are NGINX with dynamic upstreams, Envoy, Kong, or AWS API Gateway depending on your infrastructure. The choice matters less than the discipline: every extracted service goes through this proxy, and rollback must be achievable in under five minutes without a full deployment cycle.
Phase Three: Extract Your First Service — Carefully
Pick your first extraction target deliberately. Avoid the temptation to start with a large, complex, central domain object. Choose something at the edge of the system — a feature that has a clear input and output, minimal data dependencies, and a well-defined API surface. Good first candidates: user authentication and session management, email/notification delivery, image processing, reporting or analytics, or a feature that is actively being rewritten for other reasons.
The extraction sequence
- Add observability first. Instrument the monolith's version of the feature with request logging, error rates, and latency percentiles. You need a baseline before you can compare.
- Build the new service. Implement the equivalent functionality as a standalone service with its own database (do not share the monolith's database directly — this recreates the coupling problem).
- Run in shadow mode. Route a copy of production traffic to the new service without acting on its responses. Compare outputs with the monolith's. Fix discrepancies.
- Gradual traffic shift. Route 5% of live traffic to the new service. Monitor error rates, latency, and business metrics. Increase to 25%, 50%, 100% if metrics hold. Each step should have an observable dwell time — hours to days, not minutes.
- Retire the monolith's version. Only after the new service has been running at 100% of traffic without incidents for a meaningful period (at least one week for most features, longer for payment-critical paths).
Data Migration: The Part That Bites You
The hardest part of microservices extraction is not the service code — it is the data. Microservices should own their data, which means each service eventually needs its own database. But during migration, the new service and the monolith will both need access to the same data for a period of time.
Two patterns work here:
- Shared database (temporary): The new service reads from and writes to the monolith's database during the transition. This is easier to implement but maintains coupling. Acceptable as a temporary state with a defined deadline to resolve.
- Dual write with sync: The monolith writes to both the old database and the new service's database in parallel. The new service reads from its own database. Once the sync is verified and the monolith code is retired, dual writes stop. More complex to implement but achieves clean separation faster.
Avoid "big bang" data migration — copying all data, stopping writes, and switching over on a weekend. It requires a maintenance window, is difficult to reverse, and rarely goes as planned.
Rollback Safety: Non-Negotiable at Every Step
Each extraction step must have a documented rollback procedure that can be executed in under five minutes. This is not optional. If your team cannot describe exactly how to revert a service extraction before starting it, the extraction is not ready to begin. Rollback plans become muscle memory — and the discipline of writing them tends to surface hidden dependencies that should be resolved before cutting traffic.
Rollback mechanisms to build:
- Feature flags that instantly route traffic back to the monolith implementation
- Database snapshots taken immediately before any schema change
- Canary deployments with automatic rollback triggers based on error rate thresholds
- Runbooks tested in staging before production extraction begins
How Long Does This Actually Take?
A realistic timeline for a mid-sized monolith (50,000–500,000 lines of code, 5–20 logical domains) is 12–24 months for a meaningful decomposition — assuming one or two engineers are dedicated to the migration rather than treating it as a spare-time side project. The first three extracted services consistently take roughly twice as long as later ones, because the team is building shared patterns, deployment tooling, and operational muscle memory in parallel with the actual migration work. Plan for this explicitly rather than discovering it mid-sprint.
Frequently Asked Questions
Do we need to rewrite everything in a new language or framework?
No. Language choice for each extracted service is flexible, but introducing five different languages multiplies operational complexity quickly. The architectural boundary matters more than the language boundary. Only reach for a different language when the use case genuinely demands it — say, a high-throughput service where Go's concurrency model offers a measurable advantage over your existing stack.
When should we NOT migrate to microservices?
When your team has fewer than eight engineers, when your product is still finding product-market fit, or when the primary bottleneck is database-level rather than deployment-level. Microservices solve organizational scaling and deployment coupling problems. They introduce distributed systems complexity — service discovery, network partitions, distributed tracing — that can slow a small team down dramatically without providing commensurate benefit.
What monitoring do we need before starting?
At minimum: request logging with trace IDs, error rate dashboards per endpoint, p50/p95/p99 latency metrics, and alerting on business-critical flows. Without this baseline you cannot distinguish a problem caused by the migration from a pre-existing issue in the monolith. Building this instrumentation is step zero.
How do we handle inter-service communication?
For synchronous calls: REST or gRPC (gRPC preferred when latency and schema enforcement matter). For asynchronous communication: a message broker like Kafka or RabbitMQ avoids brittle point-to-point HTTP calls. Avoid chaining more than two synchronous service hops per user request — tail latency and failure blast radius grow faster than the feature value justifies.
If you'd rather not build it alone, see our cloud & DevOps services and software engineering team.
If your monolith has outgrown your deployment process and you need an engineering team that has navigated this before, talk to Mexilet Technologies. We work as an offshore backend development partner for software companies in the US, UK, UAE, and Europe — and migrations like this are exactly the kind of deep technical work we take on. Share your architecture and we will help you build a migration plan that respects your production uptime requirements.