Start Learning
Javaneer
Back to stage
Stage 6·Evolutionary Architecture

Fitness Functions

Automated tests for architectural characteristics: an ArchUnit rule guarding a dependency boundary, a performance budget, a coverage gate - so the qualities you care about can't silently erode.

15 min readAdvanced
On this page

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.

Guardrails and speed cameras on the mountain road

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.

Design fitness functions for a team's pain points

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.
Was this lesson helpful?
Edit this page on GitHub