Pulling a capability out of a monolith fails in planning more often than in execution: the seam is drawn through shared data, the extraction order maximizes risk, and the boundary layer leaks the monolith's mess into the new service.
Tier: Advanced · Workflow area: Legacy Modernization
An architecture-aware agent pass — grounded in the same evidence discipline as pb-21 — accelerates the three planning artifacts that matter: a seam map, an extraction order, and a drafted anti-corruption layer (ACL). Shopify's checkout extraction and Fowler's strangler-fig literature define the shape; the AI fills it in from your actual code.
Not for extractions driven by fashion: if the monolith module is well-bounded and low-change, the map will show extraction buys little — listen to it.
Map the seams with a coupling census. Ask for the boundary evidence, not an architecture essay:
Target: extract the `notifications` capability from this monolith.
Using the attached inventory and schema, produce a seam map:
1. INBOUND: every call path from the rest of the monolith into
notifications code — direct calls, signals/events, shared helpers.
Cite file:line. Classify each: clean interface / reaches into internals.
2. OUTBOUND: every notifications call into the rest of the monolith —
models, services, utils it imports. Same classification.
3. DATA: tables notifications reads/writes; for each, who else reads/
writes it (sole-owner / shared-read / shared-write). Shared-write
tables are extraction blockers — list them first.
4. HIDDEN COUPLING: shared transactions spanning the boundary, shared
caches/keys, monolith-global config or feature flags read inside.
Rank all couplings by severance difficulty. Mark ASSUMPTION where the
code doesn't prove it.
Audit as in pb-21: spot-check citations, chase every ASSUMPTION, and cross-check the DATA section against the schema and a grep for each table name — shared-write tables missed here become distributed-transaction bugs later.
Choose the strangle order. With the ranked coupling list, plan thin vertical slices, easiest-severed and lowest-risk first — the classic fig pattern grows coverage capability by capability, not layer by layer:
Propose an extraction order as strangler slices. Rules: each slice is
independently shippable and reversible; start with the lowest-coupling,
lowest-write-volume flows; shared-write tables stay in the monolith until
a dedicated data-migration slice; every slice states its rollback (flip
the routing flag back). For each slice: scope, couplings severed, new
interfaces created, risk, and a proof-of-completion check.
You own the risk ordering — the model does not know that template rendering is politically radioactive or that Q4 freezes deploys. Adjust with that context, and weight by the telemetry (extract the 50-rps path last, not first).
Draft the anti-corruption layer at the seam. The ACL is where monolith concepts get translated instead of leaked. Have the model draft it from real call sites:
Here are the 9 INBOUND call sites [pasted]. Draft the ACL:
- A narrow interface (NotificationGateway) expressing what CALLERS need,
not what the notifications internals happen to expose today.
- Translation at the boundary: monolith types (User model, Order) map to
boundary DTOs (RecipientRef, OrderSummary) — no ORM models cross.
- Every method: sync or async-with-outbox? What does the caller do on
timeout/failure? No method may silently widen to "and also return the
template object" — flag call sites whose current usage doesn't fit the
narrow interface instead of widening it.
The flagged misfit call sites are gold: each is either refactored to the narrow interface (usually) or forces an honest interface discussion (occasionally). An ACL generated by unioning current usage is just the old coupling with extra steps.
Wire the seam inside the monolith first. Before any service exists, land the gateway interface in-process: callers use NotificationGateway, the implementation still calls the old code, a routing flag exists but routes nowhere. This is the cheapest place to discover the interface is wrong — and it is a pure refactor verifiable with pb-22 characterization tests. Fitness functions keep the seam from eroding while extraction proceeds:
def test_no_new_direct_imports_of_notifications_internals():
violations = grep_imports("src/", target="notifications.internal")
assert violations <= BASELINE # ratchet: may only decrease
Package the charter and pressure-test it. Assemble: seam map (audited), slice order with rollbacks, ACL draft, data strategy for each shared table (stay / replicate-then-own / API-back), and fitness functions. Then red-team it in a fresh session: "Here is an extraction plan. Attack it: what breaks under partial failure between monolith and service? Which slice hides a shared transaction? Where will the ACL leak?" — then take the survivors to your senior engineers for the tribal-knowledge pass.
The strangler-fig's safety comes from reversible slices behind a routing seam — and the seam is only real if the ACL is narrow and the fitness function stops regressions. An extraction plan without shared-write table answers is a wish, not a plan.
pg_stat_user_tables or equivalent supplies real read/write volumes for the DATA section.