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.
Categories
Section titled “Categories”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.” |
Examples by characteristic
Section titled “Examples by characteristic”- 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.
How to introduce them
Section titled “How to introduce them”- Identify the characteristics that matter for this system, and rank them. You can’t optimise for everything. See identifying and ranking architectural characteristics.
- Write each as an objective statement. “Should be fast” becomes “p95 latency < 300 ms at 500 rps”.
- Choose where it runs: local tests, CI, deploy pipeline, or production monitoring. Earlier is cheaper.
- Start with warnings, then enforce. Make a new check visible before it blocks anyone.
- Review the set regularly. Remove checks that no longer matter and add ones that incidents reveal.
Common mistakes
Section titled “Common mistakes”- 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.