In the myth of the right architecture I argued that you stop guarding a specific design and start guarding the properties that made it good, using fitness functions — objective, automated checks that tell you whether the system still has the qualities you care about. That’s a tidy claim to make in the abstract. This is the most useful one I actually run, in code, and the way it turned out to do more than I asked of it.
The property worth protecting: a pure domain
Most layered or hexagonal designs rest on one dependency rule: the domain — your entities, your business rules, the model of the problem — depends on nothing infrastructural. No messaging framework, no ORM, no web stack. Everything volatile points inward at the domain; the domain points at nothing but itself.
Everyone nods along to that in a review. Then six months and forty pull requests later, someone imports a convenience helper from the messaging library into a domain type because it was right there and the deadline wasn’t, and the rule is quietly dead. Not because anyone decided to kill it — because a dependency rule enforced by human vigilance decays at exactly the rate humans get busy.
A fitness function is that rule, made executable
The fix is to turn the wish into a test. Architecture-testing libraries let you reflect over your compiled assemblies and assert dependency rules as ordinary unit tests:
[Fact]
public void Domain_depends_on_nothing_but_the_ddd_building_blocks()
{
var result = Types.InAssembly(typeof(Order).Assembly) // the Domain assembly
.Should()
.OnlyHaveDependenciesOn(
"MyCompany.DomainDrivenDesign", // our DDD base types
"System") // and the BCL — nothing else
.GetResult();
Assert.True(result.IsSuccessful,
$"Domain leaked a dependency: {string.Join(", ", result.FailingTypeNames)}");
}
Now the rule can’t rot. The day a using SomeMessagingFramework; lands in a domain type, the
build goes red with the offending type named, in CI, in front of the person who wrote it — not in
an architecture review nine months later when it’s load-bearing and expensive to unpick. The
diagram stopped being a wish and became something the build can prove or disprove.
The move that pays off most is making the test shared across the whole estate. Rather than each service reinventing the rule, a single base fixture encodes it once:
// Lives in a shared package; every service's Domain test suite inherits it.
public abstract class DomainPurityTests<TDomainMarker>
{
[Fact]
public void Domain_stays_pure() { /* the dependency assertion above */ }
}
// In each service:
public class OrdersDomainPurityTests : DomainPurityTests<Order> { }
One rule, inherited everywhere, so architectural consistency is a property of the fleet rather than of whoever happened to review the PR. New service, one line, same guarantee.
The bit I didn’t expect: it steers the design
Here’s what turned me from “nice guardrail” to “wouldn’t build without it”. A fitness function that only ever says no is just an annoying reviewer. A good one channels you toward the better structure — the constraint does design work.
We hit this correlating a long-running workflow off a domain event. The obvious move was to
decorate the domain event with the messaging framework’s identity attribute so the workflow engine
could route it. Reach for that and the domain-purity test goes red instantly, because the attribute
drags a framework using into the domain.
The red test forced a better shape. Instead of polluting the event, we kept the domain event a pure record and added a small translator in the application layer: it consumes the pure event and emits a framework-shaped command the workflow engine understands. The domain stayed clean, the framework flavour stayed in the layer that’s allowed to know about it — and, as a bonus, that translator became the obvious home for the feature-flag branch between the new path and the legacy one. The fitness function didn’t just catch a mistake; it pushed us to a seam we were better off having. That’s the difference between a rule that nags and one that teaches.
Fitness is architecture; the diagram is a snapshot
This is exactly the shift the myth post was reaching for. The boxes-and-arrows picture is a snapshot of an intended structure; it goes stale the moment the code moves on. The fitness function encodes the rule that made the picture true — “the domain depends only on itself” — and then the code underneath can evolve freely, refactor, grow modules, split things out, as long as it keeps passing. You’ve stopped guarding a drawing and started guarding the invariant. It’s the same instinct behind keeping module boundaries real so a modular monolith can stay modular: a boundary you don’t test is a boundary that has already started dissolving.
Domain purity is the one I’d start with, but the family is broad — and all of them are the same kind of test:
- No cross-module reach-in — module A may use module B’s public surface, never its internals.
- No dependency cycles between modules or layers.
- Handlers, endpoints, and the like live where they’re supposed to, by namespace convention.
- The public contract lives only in the assembly that’s allowed to expose it, so nothing private leaks across a boundary other teams build on.
The honest limits
Fitness functions are a floor, not a ceiling, and it’s worth being clear about what they don’t do:
- They check structure, not sense. An arch test proves the domain doesn’t reference infrastructure. It cannot prove the domain model is any good — that’s still on you and the reviews. Passing the test is necessary, never sufficient.
- You will need a whitelist, and it’s where discipline leaks. Real systems have a pragmatic shim or two the domain is genuinely allowed to touch. Whitelisting it is fine; the danger is the whitelist quietly growing until the rule means nothing. Every addition to it deserves the scrutiny of a one-way door, because that’s what it is.
- Over-specify and they turn brittle. A test that pins the exact shape of every namespace breaks on every healthy refactor and trains people to “fix” it by loosening it. Assert the invariant that matters, not the incidental layout.
Used well, though, a fitness function is the cheapest architecture governance there is. It runs on every build, it never forgets, it never waves something through because it’s Friday, and the best of them leave your design better than they found it. That’s what it depends looks like once you’ve decided what you’re optimising for and want the build to hold you to it.