Skip to main content

Fitness Functions and Automated Architecture Checks

What This Concept Is

A fitness function is an automated, repeatable check that measures how close a system is to an architectural claim. "The orders service must never make a synchronous call to billing" is a claim; the fitness function is the code that scans the dependency graph, counts offending calls, and fails the build if the count rises above zero.

Fitness functions live in the gap between aspirational architecture ("we believe in loose coupling") and enforced architecture ("this PR cannot merge until the coupling rule is satisfied"). They turn an ADR from a document into an executable standard.

Common shapes:

  • Static code checks (ArchUnit, dependency-cruiser): "module A must not depend on module B"
  • Metric thresholds: p99 latency < 500 ms; error rate < 0.1%
  • Contract tests: API schemas for producer and consumer must match
  • Compliance scans: no secrets in source; TLS 1.2+ on all endpoints
  • Coverage/shape: no cyclic dependencies; no function over N complexity points

Why It Matters Here

Architectural rules rot without enforcement. A claim made in an ADR and not checked by any system will be violated within a quarter, and the violation will be harder to fix later than the ADR is to edit. Fitness functions are how you make the claim real.

They also convert review effort into one-time work. Writing a rule once and running it on every PR is cheaper than re-flagging the same violation in code review forever.

Concrete Example

ADR-0052: Orders service must not call Billing synchronously; all billing operations must be asynchronous via the event bus.

Fitness function (ArchUnit-style pseudo-code):

noClasses().that().resideInAPackage("..orders..")
.should().dependOnClassesThat().resideInAPackage("..billing.client..")
.check(importedClasses);

Where it runs: CI for the orders service. Fails the build on violation. Fails the daily scheduled check in production codebase if drift is introduced by direct commits.

Exemption mechanism: violations must be tagged with an expiring @ArchRuleExemption(reason = "...", expires = "2026-09-01") annotation, tracked in a dashboard. Exemption without expiration is itself a rule violation.

That is ~10 lines of code defending a rule that would otherwise take a week of review-meeting-time per year to maintain.

Common Confusion / Misconception

"Fitness function = unit test." Not quite. Unit tests check behavior; fitness functions check architectural properties - shape, dependencies, thresholds. They often live in the same CI pipeline but target different invariants.

"All rules can be automated." Not yet. "Code is readable to the next maintainer" is an architectural claim that resists automation. The important work is separating claims you can check now from claims that need human review.

"If it passes the fitness function, the architecture is fine." Fitness functions cover what you knew to check. Drift in unmonitored dimensions is still possible.

"Fitness functions make reviews unnecessary." They change what reviews focus on: away from enforcing known rules and toward evaluating new tradeoffs. Reviews still happen; they are shorter.

How To Use It

Translate each architectural claim into one of three states:

  1. Automatable now. Write the fitness function this sprint. Gate CI on it.
  2. Automatable with effort. Draft the rule; open a ticket; estimate cost vs value of enforcement.
  3. Not currently automatable. Document the rule, mark it human-review-only, revisit quarterly.

For each fitness function:

  • link the ADR whose claim it enforces
  • specify pass/fail criteria unambiguously
  • decide where it runs (PR CI, nightly, production monitor)
  • define the escape hatch (exemption annotation, waiver process) and make exemptions visible

Check Yourself

  1. Pick one architectural rule from your team's codebase. Which of the three states is it in? Why?
  2. Why is "we should not use direct database access across service boundaries" hard to fully automate? What portion can be automated?
  3. What is the risk of a fitness function that runs only nightly versus on every PR?

Mini Drill or Application

Pick one architectural claim your team has already made (or one you would make). Design a fitness function for it:

  • one sentence: what claim does this check?
  • what is the automation mechanism (static analysis, integration test, metric threshold)?
  • where and when does it run?
  • what is the exemption path, and how are exemptions surfaced?
  • implement a prototype in <= 2 hours and run it once against your codebase

If the first run surfaces violations, you have just learned that the claim has been lying in writing.

Read This Only If Stuck