Um momento
0xE0Lesson 15 of 15

Capstone: strangle the monolith

Migrate Galactic Noodle Express off its monolith one piece at a time: plan the extraction order, route traffic gradually, and prove the new services match before switching.

45 min 7-question quiz 3 code exercises
By the end of this lesson you can
  • Plan an incremental migration with the strangler fig pattern
  • Route a growing share of traffic from the monolith to new services
  • Compare old and new behavior with shadow traffic before switching

The board has approved it: Galactic Noodle Express is leaving the monolith. The tempting plan is the big-bang rewrite - freeze features for a year, rebuild everything, switch over on one terrifying night. It almost always runs late, misses the hundreds of edge cases the old code quietly handled, and the business can’t wait a year for new features.

Instead you’ll use the strangler fig pattern, named after a vine that grows around a tree until it can stand on its own. A routing layer sits in front of the monolith; one capability at a time is rebuilt as a service; traffic for that capability moves over gradually; and the monolith shrinks until it can be switched off. At every step the system works and the move can be reversed.

The plan

Your migration toolkit has three parts - one per exercise:

  1. The extraction order. Extract a capability only after everything it calls has moved out, so the new service never reaches into the monolith’s internals. Among the ready ones, start where change is most frequent - that’s where independent deploys pay off first. A dependency cycle means the boundaries need rethinking before anything moves.
  2. The strangler router. The gateway routes each path prefix to a service or the monolith, and a percentage dial moves traffic gradually: 1%, 10%, 50%, 100%. Hashing a stable ID (the customer, not the request) keeps each customer on one side, so nobody bounces between old and new behavior.
  3. Shadow comparison. Before the dial moves, the router sends a copy of real traffic to the new service as well, discards its answers, and compares them with the monolith’s. Only when they match (ignoring fields like timestamps) does the new service take real traffic.
sticky_rollout.py
1import zlib
2
3def bucket(customer_id):
4    return zlib.crc32(customer_id.encode()) % 100
5
6for customer in ["cust-7", "cust-8", "cust-17"]:
7    sides = ["new" if bucket(customer) < percent else "monolith" for percent in (10, 50, 100)]
8    print(customer, bucket(customer), sides)
Output
cust-7 2 ['new', 'new', 'new']
cust-8 27 ['monolith', 'new', 'new']
cust-17 90 ['monolith', 'monolith', 'new']

A customer’s bucket never changes, so as the percentage rises customers only ever move from the monolith to the new service - never back and forth. zlib.crc32 is used instead of Python’s hash(), which is randomized per process.

The routing layer in production

In production the router is usually the API gateway or a service mesh. A weighted route with Kubernetes’ Gateway API looks like this:

menu-route.yaml
1apiVersion: gateway.networking.k8s.io/v1
2kind: HTTPRoute
3metadata:
4  name: menu
5spec:
6  parentRefs: [{name: public-gateway}]
7  rules:
8    - matches: [{path: {type: PathPrefix, value: /menu}}]
9      backendRefs:
10        - {name: menu-service, port: 80, weight: 10}
11        - {name: monolith, port: 80, weight: 90}
12    - matches: [{path: {type: PathPrefix, value: /}}]
13      backendRefs:
14        - {name: monolith, port: 80}

Where to go next

You’ve covered the whole journey, from boundaries to sagas to canaries. To go further:

  • Build two small services for real - say, Menu and Ordering - with a broker between them, run them with Docker Compose, and add OpenTelemetry tracing.
  • Read about domain-driven design (event storming is a fun workshop for finding boundaries) and Building Microservices by Sam Newman.
  • Try the Distributed Systems, Docker and Kubernetes tracks for the foundations underneath.
  • And remember the first lesson: a well-structured modular monolith is often the right answer. Use microservices when team and scaling needs justify them.

Key takeaways

  • Avoid big-bang rewrites; strangle the monolith one capability at a time behind a routing layer.

  • Extract capabilities whose dependencies have already moved, starting where change is most frequent.

  • Dial traffic gradually with sticky, hash-based routing, and keep the switch reversible.

  • Prove equivalence with shadow traffic, and plan each service’s data migration.

Lesson quiz

7 questions · pass with 5 correct · up to 50 XP

Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.

Practice: simulate microservice patterns in Python

Build small Python simulations of the patterns - routers, sagas, outboxes, circuit breakers, traces - and run them against sample inputs. They run locally in your browser; no servers or containers needed.

Exercise 1

Plan the extraction order

+25 XP

Each input line describes a module of the monolith: name changes_per_month: calls... - the modules it calls (possibly none). Plan the extraction in waves: a module is ready when every module it calls has been extracted in an earlier wave. Each wave extracts every ready module, listed by changes per month (most first, ties alphabetically).

Print wave 1: loyalty (35), menu (20) for each wave. If modules remain that can never become ready, print stuck: ordering, payments (a dependency cycle blocks them - rethink these boundaries) with the stuck modules alphabetically. Finally print extracted N of M modules.

  • Galactic Noodle Express
  • A tangle
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Exercise 2

Build the strangler router

+25 XP

The input has routes prefix service percent, ---, then requests customer_id path. Each request goes to the route with the longest prefix that matches (the path equals the prefix or starts with prefix + /). It goes to that route’s service if zlib.crc32(customer_id.encode()) % 100 < percent, otherwise to monolith. Paths no route matches go to monolith.

Print cust-42 /menu/dishes -> menu-service, then one line per destination with its request count, menu-service: 2, ordered by count (most first, ties alphabetically).

  • Mid-migration
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Exercise 3

Compare shadow traffic

+25 XP

The first input line is threshold ignored_fields, like 95 generated_at,trace_id. Each following line is a JSON object {"request": ..., "monolith": {...}, "service": {...}} with the two flat responses to the same request.

Compare each pair, ignoring the listed fields: print match GET /menu/42, or MISMATCH GET /menu/7: price_cents 1450 != 1500; spicy missing in service - listing differing fields alphabetically, as field old != new, field missing in service or field only in service. Then print match rate: 75.0% and ready to switch if the rate is at least the threshold, otherwise keep shadowing.

  • Menu service shadow run
  • All clear
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Questions about this lesson

Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.

Loading posts…

Gostou da aula? 😆👍
Apoie nosso trabalho com uma doação: