Most advice about refactoring monoliths focuses on the redesign: extract this service, define this boundary, pick this migration pattern. That's the easy 20% of the problem. The hard 80% is what happens in the six to eighteen months afterward, when forty different engineers are shipping PRs against the newly-refactored structure, most of them with no memory of why the boundary was drawn where it was.
A refactor that isn't defended after the fact isn't a refactor. It's a temporary state the codebase passes through on its way back to where it started.
The decay pattern is completely predictable
Here is what almost always happens, in order:
- A clean boundary is established — say, a payments module that shouldn't import directly from the user module.
- The shortcut under pressure: Three months later, someone needs "just one field" from the user module inside a payments function, under deadline pressure, and imports it directly rather than going through the intended interface.
- Pattern imitation: That import gets copied as a pattern by the next engineer who needs something similar, because it's now "how it's done here."
- Boundary erosion: A year later, the boundary that was supposed to define the refactor exists only in a design doc nobody reads.
Enforcement has to happen where the decision gets made
The only point in the lifecycle where enforcing a boundary is cheap is the pull request that's about to violate it. Once that PR merges, the violation is now load-bearing — other code may already depend on the shortcut, and "fixing" it means a second, harder refactor to undo the first one's erosion.
This is why architectural review needs to be a standing part of PR review, not a periodic audit. An agent scoped specifically to architectural consistency — checking imports against intended module boundaries, flagging responsibility drift, catching a data-access pattern that bypasses an established interface — catches exactly the step-4-in-waiting import in step 2, while it's still a one-line fix.
Refactor in reviewable increments, not in one branch
A common failure mode is doing the entire monolith decomposition on a long-lived branch, then merging it in one enormous PR. This feels safer, but it defeats the actual purpose of code review, because no reviewer can meaningfully evaluate a 4,000-line structural change in one sitting. It gets rubber-stamped, not reviewed.
The alternative — incremental extraction, one bounded module at a time, each as its own reviewable PR — is slower to feel finished but dramatically safer, because every step is small enough that a reviewer (human or automated) can actually reason about whether it preserves the intended boundary.
The unglamorous truth about successful refactors
The monolith decompositions that actually stick aren't the ones with the most elegant target architecture. They're the ones where someone made boundary violations visible and correctable at the exact moment they were about to happen, consistently, for long enough that "importing through the interface" became the only pattern anyone had ever seen.
Mid-refactor and worried about drift creeping back in? Codeward's architecture agent flags boundary violations at PR time, so the refactor you shipped in Q1 is still the architecture you have in Q4.