You drew the boundaries. You named the bounded contexts, wrote the ubiquitous language, and drew a beautiful context map on the whiteboard. Six months later, half the codebase imports directly from the other half and your “bounded contexts” are a boundary in name only. The uncomfortable truth: a boundary you don’t enforce isn’t a boundary — it’s a suggestion. The fix is to make your architecture measurable and self-policing with architecture fitness functions and a couple of old-but-gold coupling metrics.
This is a practical guide to proving — in CI, on every commit — that your DDD boundaries are still holding, so drift fails the build instead of rotting silently for two quarters.

🧪 Fitness functions: turn “the senior would’ve caught that” into “the build caught that”
A fitness function is an automated, objective test of an architectural characteristic — not “does this feature work” but “does the system still honor a structural rule we decided on.” Coined in Building Evolutionary Architectures, the idea is simple and powerful: encode your architectural decisions as executable checks that run in CI and fail the build on violation.
For DDD, the single highest-value fitness function is the boundary rule: “Code in the Billing context may not import from the Shipping context except through its published interface.” Written once as a test, it converts a rule that used to live in a senior engineer’s head (and got enforced inconsistently in review) into a deterministic gate that catches every violation, from every contributor — human or AI agent — on every commit.
That’s the whole game: architecture stops being a diagram people ignore and becomes a property the build guarantees.
🛠️ The tools, by language
You don’t build this from scratch — every major ecosystem has a dependency-rule tool:
- ArchUnit (Java/Kotlin) — write architecture rules as plain unit tests (“no class in
..billing..may depend on..shipping..“). - Deptrac (PHP) — declare layers/contexts and the allowed dependencies between them; it fails on any violation.
- import-linter (Python) — define “contracts” (forbidden imports, layered architectures) enforced in CI.
- NetArchTest (.NET) — fluent assertions about type dependencies in test form.
- go-arch-lint (Go) — a config-driven component dependency linter.

Pick the one for your stack, express your context map as its rules, and wire it into CI next to your unit tests. A cross-context import that used to slip through now turns the pipeline red.
📐 Coupling metrics: the numbers behind “decoupled”
Fitness functions catch hard violations. Metrics tell you whether a context is drifting toward trouble before it breaks a rule. Robert C. Martin’s package metrics are the classic, still-useful lens:
- Afferent coupling (Ca) — how many things depend on this context (incoming). High Ca means lots of others rely on you: you’re stable, and changing you is expensive.
- Efferent coupling (Ce) — how many things this context depends on (outgoing). High Ce means you’re at the mercy of many others.
- Instability
I = Ce / (Ca + Ce)— ranges 0 (maximally stable, everyone depends on you, you depend on no one) to 1 (maximally unstable). Neither extreme is “bad”; the point is that stable packages should be abstract and unstable ones concrete. - Abstractness (A) — the ratio of abstract types to total types in the context.
- Distance from the Main Sequence
D = |A + I − 1|— the headline number. Martin’s insight is that healthy components sit near the lineA + I = 1: stable and abstract, or unstable and concrete. Drift far off it and you land in one of two bad zones — the Zone of Pain (stable and concrete: everyone depends on it, but it’s rigid and hard to change) or the Zone of Uselessness (abstract but nobody uses it).

You don’t need to obsess over these daily. You need to snapshot them over time and watch the trend: a core bounded context whose afferent coupling keeps climbing and whose distance D is creeping into the Zone of Pain is telling you, quantitatively, that it’s becoming a bottleneck the whole system is stuck to.
📉 Detecting drift before it compounds
Boundaries erode one “just this once” import at a time. Three habits keep that in check:
- Block new violations in CI. The fitness function is the gate — a new cross-context dependency can’t merge. This is the single most important control.
- Snapshot metrics per release. Track Ca/Ce/
Dper context over time so slow erosion shows up as a trend line, not a surprise. - Govern the context map. Treat the allowed dependencies between contexts as a reviewed artifact (it’s literally the config for your dependency-rule tool). Changing what’s allowed is an architectural decision that gets reviewed — not something that happens silently because someone added an import.
If you’re running a fleet of AI coding agents, this matters double: agents will cheerfully wire two contexts together to make a feature work, because a diff-level view can’t see that the change is architecturally wrong. The fitness function is what stops them — the same “context is probabilistic, configuration is deterministic” principle behind the rest of our agentic-loops work. See mutation testing for AI-generated code for the test-quality half of the same story, and DDD for AI coding agents for why bounded contexts are the agent’s natural work-unit.
⚠️ Honest caveats
- Don’t let the gate kill velocity. Start with the one or two boundary rules that matter most; a wall of pedantic architecture tests trains people to ignore them.
- Metrics are signals, not verdicts. A high instability number isn’t a bug — a concrete, unstable adapter should be unstable. Read the metrics in context, not as pass/fail.
- Governance has a cost. Maintaining the allowed-dependency config and reviewing changes to it is real work; budget for it, and keep the rules few and meaningful.
❓ Frequently Asked Questions
What is an architecture fitness function? An automated, objective test of an architectural characteristic — like “no cross-context imports” or “the domain layer may not depend on the web layer” — that runs in CI and fails the build when the rule is violated. It turns architectural intent into an enforced gate.
How do I enforce bounded-context boundaries in code? Use a dependency-rule tool for your stack — ArchUnit (Java), Deptrac (PHP), import-linter (Python), NetArchTest (.NET), or go-arch-lint (Go) — to declare which contexts may depend on which, and run it in CI so any forbidden import fails the pipeline.
What is the “distance from the main sequence”? D = |A + I − 1|, where A is abstractness and I is instability. It measures how far a component is from the ideal balance of “stable and abstract” or “unstable and concrete.” Large distances land you in the Zone of Pain (rigid but depended-on) or the Zone of Uselessness (abstract but unused).
Are coupling metrics worth tracking for DDD? Yes, as a trend. Snapshotting afferent/efferent coupling and distance-from-main-sequence per context over time reveals boundary erosion before it becomes a crisis — but treat the numbers as signals to investigate, not automatic verdicts.
📚 Further Reading
- What Is Domain-Driven Design? — the concepts these metrics protect
- Domain-Driven Design for AI Coding Agents — why boundaries matter more with agents
- Mutation Testing for AI-Generated Code — the test-quality gate that pairs with these architecture gates
📚 Recommended Resources
Disclosure: some links below are Amazon affiliate links. If you buy through them, I earn a small commission — at no extra cost to you — which helps keep this site running.
Enforcing architecture is equal parts DDD and evolutionary-architecture discipline. These build both.
- Building Evolutionary Architectures (2nd ed.) — Ford, Parsons, Kua & Magee · Book. The source of the fitness-function idea — how to make architectural governance automated and continuous.
- Learning Domain-Driven Design — Vlad Khononov · Book. Where the bounded contexts you’re measuring come from — strategic design done approachably.
- A Philosophy of Software Design — John Ousterhout · Book. Deep vs. shallow modules and complexity — the design taste behind low coupling.
- Clean Architecture — Robert C. Martin · Book. The source of the coupling metrics (Ca/Ce/instability/main sequence) this article uses.
- Accelerate: The Science of Lean Software and DevOps — Forsgren, Humble & Kim · Book. Why measuring the right architectural signals beats optimizing gameable proxies.