← Concepts

阅读中文版本

Fitness Functions: Automated Checks on Architecture Quality

A fitness function is an automated test of an architectural property, turning vague goals like loose coupling into checks that fail the build.

architecture

Definition

A fitness function is an objective, often automated check that evaluates whether a system still satisfies an architectural characteristic, such as module independence, latency budgets, or dependency direction, and reports a pass or fail.

Context and scope

Architecture decays gradually; fitness functions catch the drift at the pull request instead of during a quarterly review or a postmortem.

Common misconceptions

  • Fitness functions are unit tests — they test structural properties, not behaviour
  • They must be perfect — a check that catches most violations is already useful
  • Only static analysis counts — runtime metrics such as p95 latency can be fitness functions too

Anti-patterns

  • Writing fitness functions nobody runs in CI
  • Encoding a preference as a hard rule without an exception process
  • Measuring a proxy that teams can satisfy without improving the property

Related concepts

Related examples

Authoritative source

https://www.thoughtworks.com/books/building-evolutionary-architectures

Tests for architecture

An architectural goal such as “services must be independently deployable” is a wish until something checks it. A fitness function converts the wish into a verdict: this module may not import that one, this endpoint must answer within 200 milliseconds at p95, this service must not share a database schema with another. The check runs on every change, so the property is either true now or the build is red.

Static and runtime checks

Static checks read code and configuration: import graphs, dependency direction, forbidden annotations, schema ownership. They are fast and deterministic. Runtime checks read telemetry: latency budgets, error-rate ceilings, coupling measured by call fan-out. Runtime checks catch properties that static analysis cannot see, but they are noisier and need a stable window. Many teams use both, with static checks as the gate and runtime checks as the alarm.

// fail the build if the domain layer imports infrastructure code
expect(moduleGraph(domain)).not.toImport('src/infra/**');

Choosing properties

Pick characteristics that matter to the business and are at risk of eroding: modularity, latency, security posture, data ownership. Start with one or two checks and make them trustworthy before adding more. A fitness function that fails intermittently teaches the team to ignore it, which is worse than having none.

Handling violations

Every check needs an exception path with an owner and an expiry. A hard rule with no escape hatch will be disabled during the first urgent release. For example, a legacy module may be allow-listed for six weeks with a tracking issue, and the list reviewed like any other debt. When the exception expires, the build fails again.

Compared to code review

Code review catches what a reviewer remembers to look for; fitness functions catch the same property every time. Review still matters for taste and context, but it is a poor enforcement mechanism for rules that can be computed. Automate the computable rules and spend human attention on the rest.