Fitness-Funktionen
Automatisierte Tests für architektonische Eigenschaften: eine ArchUnit-Regel als Grenzwächter, ein Performance-Budget, ein Coverage-Gate - damit die Qualitäten, die zählen, nicht still erodieren.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
If architecture evolves, how do you stop it from evolving badly - the domain leaking into the UI, cycles creeping between modules, latency slowly climbing? You can't rely on everyone remembering every rule. The answer is a fitness function: an automated, objective test that verifies an architectural characteristic and fails the build when it's violated. It's the idea behind ArchUnit (which you've already met) generalized to every quality you care about.
Borrowing a term from evolution
In evolutionary computing, a fitness function scores how close a candidate solution is to the goal, guiding each generation toward it. Applied to architecture, a fitness function is any mechanism that objectively assesses whether the system still meets a desired architectural characteristic - and, crucially, one you can automate so it runs continuously. It's how you guide the system's evolution instead of hoping it drifts the right way.
The insight is that most "-ilities" you care about can be turned into a checkable rule:
- Maintainability → "no module depends on another's internals" (a dependency rule).
- Performance → "the checkout endpoint's p99 stays under 300 ms" (a performance budget).
- Reliability → "no cyclic dependencies that could cascade failures."
- Security → "no domain class imports a framework known to have leaked secrets," or a dependency-vulnerability scan.
Structural fitness functions with ArchUnit
The most common fitness functions guard structure, and you already saw the tool - ArchUnit - in the modular monolith module. Reframe it: each ArchUnit rule is a fitness function for a structural characteristic:
@Test
void domain_stays_pure() { // fitness function for maintainability
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat().resideInAnyPackage("..web..", "jakarta.persistence..")
.check(importedClasses); // fails the build if the domain touches the web or JPA
}
@Test
void no_cyclic_dependencies() { // fitness function for evolvability
slices().matching("com.ledger.(*)..").should().beFreeOfCycles().check(importedClasses);
}These run on every commit. The Dependency Rule from hexagonal architecture, the module boundaries from the modular monolith - all become executable guardrails rather than tribal knowledge that erodes.
Beyond structure: performance, security, ops
Fitness functions aren't only ArchUnit. Any automated check of a characteristic qualifies:
- Performance budgets - a load test in CI asserting p99 latency or throughput thresholds; a check that the built artifact stays under a size limit.
- Security gates - dependency vulnerability scans (OWASP, Snyk) failing the build on a critical CVE; a check that no secret patterns are committed.
- Coverage / quality gates - minimum test coverage, or a mutation-testing score (a later path).
- Operational - a synthetic monitor in production continuously verifying an SLO (an ongoing fitness function that runs forever, not just in CI).
They split into triggered (run on a commit/build) and continual (run constantly against the live system, like an SLO monitor). Together they form a safety net across all the qualities you decided matter.
Why they work: eroding qualities become impossible, not just discouraged
The power is the same as any enforced boundary: a fitness function converts "please keep the domain pure" (a wish people forget under deadline) into "the build fails if you don't" (a guarantee). Architectural characteristics erode through a thousand small, individually-reasonable compromises; a fitness function catches each one at the moment it's introduced, when it's cheap to fix, rather than after months of accumulated drift. You encode your architectural decisions as tests, and the architecture defends itself.
Start with a few high-value fitness functions
You don't need dozens on day one. Pick the handful of characteristics that would hurt most if they eroded - your key dependency boundaries, a critical latency budget, a vulnerability scan - and encode those first. A few well-chosen fitness functions running in CI catch the most damaging drift. Add more as you notice a quality you care about slipping. The goal is a living safety net, not a bureaucratic checklist nobody maintains.
Return to the mountain road. Good intentions - 'everyone, please stay in your lane and under the speed limit' - fail the moment someone's tired or rushed. Fitness functions are the guardrails, rumble strips, and speed cameras installed along the road: they don't rely on anyone's vigilance, they physically or automatically enforce the safe envelope, and they flag a violation the instant it happens, not after a crash. A structural ArchUnit rule is the guardrail that won't let you drift off the dependency edge; a performance budget is the speed camera that tickets latency the moment it creeps over the limit. The road (architecture) still evolves and gets rerouted - but always within guardrails that keep the evolution safe.
A team keeps having three recurring problems: (1) developers occasionally import a UI class into a domain class, re-coupling the layers; (2) a critical search endpoint has slowly degraded from 200 ms to 900 ms over months without anyone noticing until users complained; (3) a vulnerable logging library shipped to production because nobody checked. Propose a fitness function for each and say whether it's triggered or continual.
What is an architectural fitness function?
Key takeaways
- A fitness function is an automated, objective check that a system still meets a desired architectural characteristic, failing when it's violated.
- Most '-ilities' become checkable rules: maintainability as dependency rules, performance as latency budgets, security as vulnerability scans, evolvability as no-cycles.
- ArchUnit rules are structural fitness functions - the Dependency Rule and module boundaries become executable guardrails, not tribal knowledge.
- They split into triggered (run per commit/build in CI) and continual (run constantly against the live system, like an SLO monitor).
- Fitness functions turn 'please don't erode this' into 'the build fails if you do,' catching each small compromise the moment it's introduced; start with a high-value few.