Skip to content

Fitness functions

The term comes from evolutionary computing, where a fitness function scores how close a candidate solution is to the goal. In architecture, a fitness function is any mechanism that gives an objective assessment of how well the system meets an architectural characteristic.

“Any mechanism” is broad on purpose. A unit test, a metric with an alert, a linter rule, a policy-as-code check, a chaos experiment, and even a manual review checklist can all be fitness functions. What matters is that they are objective: two people running the check get the same answer.

The book describes fitness functions along several axes. A single check usually sits somewhere on each.

Axis Options Example
Scope Atomic: one characteristic in one context. Holistic: several characteristics interacting. Atomic: “no cycles between packages”. Holistic: “latency stays under 200 ms while the security scanner is enabled”.
Cadence Triggered: runs on an event (commit, deploy). Continual: runs all the time. Temporal: runs on a schedule or reminder. Triggered: a test in CI. Continual: production monitoring. Temporal: “review this library’s version every quarter”.
Result Static: fixed pass/fail threshold. Dynamic: threshold depends on context. Static: coverage ≥ 80%. Dynamic: allowed latency rises with load.
Execution Automated or manual. Manual: a legal/compliance sign-off that can’t be automated yet.
Origin Intentional: defined up front. Emergent: discovered as the system runs. Emergent: a new check added after an incident.
Coverage Domain-specific checks tied to a business concern. “Every payment event is auditable.”
  • Modularity / structure: ArchUnit (Java), NetArchTest (.NET), dependency-cruiser (JS/TS), import-linter (Python) to block forbidden dependencies and cycles.
  • Performance: load tests in the pipeline with a latency budget; production SLO alerts.
  • Security: dependency vulnerability scans, secret scanning, “no PII in logs” checks, IaC policy checks.
  • Resilience: chaos experiments (instance loss, AZ failure) with a pass condition.
  • Contracts between services: consumer-driven contract tests (for example Pact).
  • Cost: budget thresholds per environment, cost estimates on pull requests.
  • Operability: every service exposes health checks, structured logs, and trace context.
  1. Identify the characteristics that matter for this system, and rank them. You can’t optimise for everything. See identifying and ranking architectural characteristics.
  2. Write each as an objective statement. “Should be fast” becomes “p95 latency < 300 ms at 500 rps”.
  3. Choose where it runs: local tests, CI, deploy pipeline, or production monitoring. Earlier is cheaper.
  4. Start with warnings, then enforce. Make a new check visible before it blocks anyone.
  5. Review the set regularly. Remove checks that no longer matter and add ones that incidents reveal.
  • Checks that are flaky get ignored and then removed. Treat them like production code.
  • Checks with no owner rot. Each one should have someone who can explain why it exists.
  • Writing checks for characteristics nobody actually prioritised gives you friction without benefit.