APIs, integration & security — in depth

Monolith to Microservices Migration Patterns

Decide which pattern fits your bottleneck before you start extracting.

Senior Writer · · 11 min read
Cover illustration for “Monolith to Microservices Migration Patterns”
Integration Architecture · October 2, 2026 · 11 min read · 2,493 words

Monolith migrations usually start for one of three reasons: components that refuse to scale on their own, CI/CD pipelines that choke on unrelated changes to the rest of the codebase, and team coordination costs that grow faster than the product itself. Those are real pains, and the payoff can be real too. One reputation management company cut roughly $14,000 a month in infrastructure costs after migrating, and a B2B SaaS company took its deployment cycle from six weeks down to four days after moving a Flask monolith to Python microservices built on FastAPI.

Segment migrated into a large fleet of microservices, then wrote a public engineering post explaining why it was consolidating back into a monolith, naming operational complexity, exploding defect rates, and collapsing development velocity as the reasons. Segment isn't alone. A meaningful share of organizations that adopted microservices are now folding some of them back into modular monoliths specifically to reduce complexity. None of this means microservices were the wrong call. A lot of teams picked the architecture before they picked the pattern, and the pattern decides whether the thing works. Senior engineering consensus is blunt on when to migrate at all: do it when coordination overhead, scaling limits, or fault isolation have become observable, measurable bottlenecks, rather than because microservices are the industry's default setting this year. Everything that follows in this piece assumes a team has already cleared that bar and is now facing the harder question: which pattern, in which order.

Why big-bang rewrites fail

A full rewrite fails for a boring, structural reason: the old system can't take new features while the rewrite is underway, so every week spent rewriting is a week the monolith and the new system drift further apart. Competitors keep shipping while the rewrite team is heads-down. And the replacement system quietly inherits assumptions from the old one that were never written down anywhere, because nobody had to write them down when there was only one system to maintain.

The numbers back this up without needing much interpretation. More than half of initial microservices migration projects fail to hit their objectives, and the reason is almost never that microservices were the wrong architecture, it's poor planning and underestimated operational overhead. Costs and timelines get underestimated just as often, with real migrations running into the millions of dollars and stretching across months or years rather than weeks. None of that is an argument against migrating. It's an argument against treating migration like a single event.

Netflix, Uber, and Samsung all followed the same pattern: none of them cut over in one move. Each extracted one boundary at a time while the monolith kept serving live traffic underneath. That's not caution for its own sake; it's the only approach that keeps the business running during the transition. And it has a prerequisite most teams try to skip: observability has to be built into the monolith before any decomposition starts, because debugging a problem that spans service boundaries without that visibility is close to impossible. Once that's in place, the real question is not "rewrite or not" but which incremental pattern to apply, in which order."

How the Strangler Fig pattern works

The Strangler Fig pattern is the one most teams reach for first, and it earns that position. It works by putting a routing layer in front of both the monolith and the new services, so the control plane for the entire migration lives outside either system. That one design choice is what makes rollback a configuration change instead of a redeployment. Martin Fowler gave the pattern its name, borrowed from the fig tree that grows around a host tree and gradually takes over its functions until the host disappears entirely. The metaphor holds up better than most engineering metaphors do.

Mechanically, a proxy or API Gateway sits in front of the monolith and the new microservices. A request for a feature that hasn't been migrated yet goes to the monolith. A request for a feature that has been migrated goes to the new service. The routing table is the whole control plane: it can split traffic, run A/B tests between the old and new implementation, and roll back instantly if something breaks, because rolling back just means changing a route. When every feature has been extracted and the routing table has nothing left pointing at the monolith, the monolith gets decommissioned, not before.

The implementation sequence is straightforward in outline: find a well-bounded module (user management, orders, and inventory are the usual starting points), build it as an independent service, wire the gateway to route to it, shift traffic, validate, then repeat with the next module. The gateway's routing logic grows more tangled with every feature migrated, and that's a real maintenance cost, not a hypothetical one. Netflix used exactly this gradual extraction, starting with server-side services before moving to registration, settings, and content selection, to keep the platform stable while its user base was growing fast, and it paid off in fault isolation during outages and the ability to scale services independently. The pattern fits best at the perimeter, where a feature already has a clean external interface. It fits poorly for components buried deep in the codebase with layers of internal dependencies, because there's no edge to route around.

When Branch by Abstraction handles what the Strangler Fig cannot

Branch by Abstraction picks up exactly where the Strangler Fig runs out of road: components buried deep in the call stack, with multiple upstream dependencies, that no routing layer can intercept because they never touch the system's edge. Instead of rerouting traffic at the perimeter, Branch by Abstraction puts an abstraction layer (an interface or adapter) directly in front of the legacy implementation. The old code and the new implementation sit behind that same interface at the same time, and traffic shifts from old to new gradually, without a hard cutover.

In practice, large migrations run both patterns at once rather than choosing between them. Strangler Fig handles the system boundary, intercepting external traffic. Branch by Abstraction works inside the codebase, modernizing the internal components that traffic eventually reaches once it's past the gateway. Skipping Branch by Abstraction is how a team ends up with a new service that looks independent on paper but is still wired into legacy internals, a distributed monolith wearing a microservices costume, unable to actually deploy or scale on its own. The Strangler Fig gets the credit for the clean extraction. Branch by Abstraction is what makes the extraction actually mean something.

How Domain-Driven Design sets migration boundaries

The most common failure in these migrations is that services get drawn along the lines of the existing code structure instead of along real business domains, and those services look independent on an architecture diagram while being unable to deploy or scale separately in practice, which produces a distributed monolith carrying all the operational cost of microservices with none of the benefit. Domain-Driven Design's bounded contexts have to define those service boundaries before decomposition starts, not somewhere in the middle of the migration once teams have already committed to a shape.

Uber's early experience is the clearest cautionary tale here. Its first migration decomposed the system into services that were too fine-grained, and the operational complexity that followed forced a correction: grouping those fine-grained services back into higher-level domains, like a Map Domain and a Fare Domain, through what Uber calls Domain-Oriented Microservice Architecture, or DOMA. The lesson wasn't that fine-grained services are wrong in principle. It's that Uber had to retrofit coarser domain groupings after the fact, which is a more expensive way to learn the lesson than defining those domains up front.

Sequencing by risk matters as much as the boundaries themselves. Smart teams start with low-risk, well-bounded modules like reporting or billing before they go anywhere near core transaction flows, because that's where a team builds migration muscle while the stakes are still manageable. The data layer is where all of this gets tested hardest: each service has to own its own data store, and services talk to each other through APIs only, never through a direct database call. Samsung's migration shows what happens when that discipline is taken seriously at scale. Its centralized Oracle monolith had become a cost and scaling bottleneck for a huge user base, and the fix was moving to Amazon Aurora instances, splitting data ownership per service, and enforcing API-only communication between services. Crossing a service boundary turns a clean ACID transaction into an eventual-consistency problem. Saga patterns and CQRS have to be planned into the architecture before the data layer gets split, not bolted on afterward. If that planning is skipped, a microservice times out, the system can't roll back the dependent records in adjacent services, and those records end up permanently corrupted, a direct consequence of splitting data before transaction boundaries were defined. Grid Dynamics ran into the scale version of this problem while migrating a restaurant chain client, building more than 60 microservices on a Kubernetes-based platform with CI/CD pipelines to unify eight major brands, a scale at which domain boundaries had to be defined and enforced before any service was built. Boundary definition is the variable that decides whether any of these patterns produces a real microservice or just a more expensive monolith. Get the boundaries right, and the next problem is operational: how to put these services into production without breaking everything the moment traffic hits them.

How Canary Release Deployment controls production risk

Correct boundaries and a sound pattern don't guarantee a safe landing. The moment traffic actually shifts onto a new service is where cascading failures happen, and Canary Release Deployment is the mechanism that makes that moment something a team can recover from instead of something that takes the system down. Even a service that's correctly bounded and thoroughly tested can behave differently under real production load, introducing latency, errors, or throughput problems that never showed up in staging, and a failed full cutover costs orders of magnitude more than a staged rollout would have.

The mechanics are simple by design. A small slice of production traffic gets routed to the new service version. Latency, error rates, and throughput get watched against set thresholds. If the metrics stay healthy, the traffic percentage increases in steps. If a threshold gets breached, rollback happens automatically, it isn't a judgment call made by someone staring at a dashboard under pressure. None of this works without observability infrastructure already in place before the first canary goes out, and that's the prerequisite teams skip most often. Practitioners commonly cite Prometheus and Grafana for exactly this purpose, monitoring traffic routing between the monolith and the new microservices during the Strangler Fig phase. Canary Release also adds a cost that's easy to underestimate going in: every microservice needs its own deployment pipeline, monitoring, logging, and on-call rotation, so a monolith that becomes a handful of services can quietly become a system with dozens of services, each demanding independent operational attention.

Without this pattern and the observability behind it, none of the other three patterns can be safely validated once real production traffic is involved. Canary Release does double duty: it's also how a team finds out its service boundary was drawn wrong, while that mistake is still cheap to fix, before a full cutover makes the mistake permanent.

The sequence that ties the four patterns into a working migration plan

Each of these four patterns has been explained on its own, but the order they get applied in matters just as much as the patterns themselves. A migration that extracts services before observability exists, or splits the database before transaction boundaries are defined, ends up in the same failure modes as a migration that picked the wrong pattern. The sequence below is what practitioner evidence converges on.

Instrumentation comes first, before any decomposition happens, because a team needs a known baseline for the monolith's behavior to have any hope of spotting anomalies once new services start appearing. Domain boundary analysis comes second, using Domain-Driven Design to define bounded contexts and identify coarse-grained domains before a single line of migration code gets written, since this is what decides where Strangler Fig and Branch by Abstraction actually get applied. Third, the Strangler Fig pattern begins at the perimeter, extracting a low-risk, well-bounded module first, something like reporting, billing, or user management, building the routing layer, and validating with Canary Release before anything else gets touched. Fourth, Branch by Abstraction gets applied to the internal coupling that the perimeter extraction exposed but couldn't reach on its own, once that first extraction is proven to work. Fifth, the data layer gets split, and only after service boundaries have proven stable in production: transaction boundaries get defined, sagas and CQRS get implemented wherever ACID consistency is no longer available, and API-only communication between services gets enforced from that point forward. Sixth, the whole sequence repeats on progressively higher-risk domains, carrying forward whatever was learned from the earlier, lower-stakes extractions, with Canary Release validating each step along the way.

Uber's granularity lesson belongs squarely inside this sequence: starting with coarser-grained domains and refining them later is operationally safer than starting fine-grained and discovering the overhead only after dozens of services are already live. Skipping straight to Step 3, extraction, without doing Step 1 and Step 2 first is the anti-pattern to avoid. Teams that do this are the ones who end up with distributed monoliths, because the boundary analysis and the observability groundwork feel like delay in the moment, when in reality they're the work that makes everything after them recoverable. The sequence holds regardless of company size or industry. What's changing is how Step 2, the hardest and slowest step, gets done.

What AI-assisted decomposition changes about boundary detection

AI-assisted decomposition tooling is aimed squarely at the step that causes the most migration failures: manual dependency analysis, which doesn't scale well once a codebase gets large. That's the exact bottleneck in Step 2 of the sequence above, the boundary analysis work that takes the longest and is hardest to shortcut by hand. That raises the question of when AI-assisted decomposition actually changes this sequence.

These tools can move faster through dependency mapping than a team of engineers tracing call graphs manually, which matters most for the organizations with the largest, oldest codebases, where manual analysis would otherwise take months. What the tooling does not yet have a track record of doing is replacing the judgment calls that Uber's DOMA correction and Samsung's data-layer discipline both depended on, decisions about which domain a service really belongs to, and how much coupling is acceptable before a boundary becomes a liability. Faster boundary detection is a genuine gain. It doesn't remove the need for the sequence itself, instrumentation first, boundaries second, perimeter extraction third, internal decoupling fourth, data splitting fifth, and repetition at higher stakes sixth. It just means Step 2 may take less time than it used to.

Sources

  1. Cloud modernization playbook: From monolith to microservices
  2. Architecture Patterns: From Monolith to Microservices - Paradigma
  3. Migration to a Microservice Architecture [Case Study] - MindK

More in Integration Architecture