Be Software Architecture Wise
Be Wise Apps

Leer en español · Lire en français · Auf Deutsch lesen

Section 11.3 of 11.8 · Advanced · 27 min read

Fitness functions and evolutionary architecture

Video lesson

5 min · narrated, with captions
Listen only (audio)
Read the transcript

An evaluation is a snapshot. The day after it ends, every change can quietly undo a decision it relied on. In this lesson you'll learn how fitness functions check those decisions automatically and continuously.

A car's dashboard has warning lights for the things that must never go wrong unnoticed: oil pressure, engine temperature, brakes. You don't wait for the annual service to find out the engine overheated in March. A fitness function is a warning light for a software architecture. The team decides which qualities really matter, such as how fast an order is confirmed, and writes an automatic check for each. Those checks run every time the software changes. When one fails, the change is stopped or someone is alerted, while the problem is still small.

The term comes from the book Building Evolutionary Architectures, by Neal Ford and his co-authors. A fitness function is any mechanism that gives an objective assessment of an architectural characteristic. Any mechanism: a unit test, a load test, a monitor, a chaos experiment, even a scheduled manual review. Objective: the result mustn't depend on who runs it. A feeling that the code is well structured doesn't count. And a metric describes, but a fitness function decides. Breaking one forces a conscious choice: fix the code, or change the rule and record why.

Here's the anatomy of one. Start with a characteristic, such as order latency, and a way to measure it: the ninety fifth and ninety ninth percentiles, at a hundred orders a second. The threshold comes straight from the scenario's response measure: one and a half seconds, and three seconds. The check is a load test in the pipeline, and the action, when the threshold is broken, is to fail the build. A fitness function should fail loudly, not fill a dashboard nobody reads. Underneath, it traces back to its source scenario. And it has an owner, a review date, and a rationale recorded in an Architecture Decision Record, or ADR.

Fitness functions run all along the delivery pipeline. Atomic checks test one characteristic; holistic checks test several together. On every build come cheap, fast, atomic checks: dependency rules, cycle checks, complexity limits, and dependencies with known vulnerabilities. In the test stage come slower, holistic checks: a load test with percentile thresholds, schema and contract checks, and a failover test. At the canary, the new version's error rate and latency are compared with the old version's. And in production, continual checks run all the time: monitors on service level objectives, or SLOs, synthetic orders, chaos experiments and cost per order. Failures and drift feed back as new work, and often as new fitness functions.

Now the platform turns three scenarios into fitness functions. First: at a hundred orders a second, ninety five per cent of orders are confirmed within one and a half seconds, and ninety nine per cent within three. Fitness function one is a load test against a production-like environment, nightly and before every release. It fails if either percentile is over its threshold, or if more than nought point one per cent of requests fail. The one and a half seconds is also split into budgets for the network, the gateway, ordering, pricing and database writes, leaving five hundred and fifty milliseconds of headroom. Each service tests its own budget, so a regression is caught where it's caused. Scenario two is checkout uptime. Each week in staging, a chaos experiment makes the payment provider hang. Orders must still be accepted as pending within ten seconds, and all must settle after recovery. Scenario three: a new point of sale vendor should take five days of one person's work, by adding one adapter. A build rule stops the restaurant and ordering code importing any vendor's software kit, so the change stays local. Each one is recorded with its source scenario, its threshold, its owner, the ADR that explains it, and a review date.

Fitness functions are half of what those authors call evolutionary architecture: guided, incremental change across multiple dimensions. Small deployable parts and fast pipelines give the increments. Fitness functions give the guidance. Start small. Pick the three to five characteristics the utility tree rates highest, and write one reliable check for each. A dozen trusted checks are worth more than a hundred that fail at random. For an existing system, use a ratchet. Record today's violations as a baseline, fail only on new ones, and tighten the baseline as the debt is paid down. And keep the pipeline fast: atomic checks in every build, and heavy holistic checks, such as ten minute load tests, nightly or before release.

The full section covers the dimensions of fitness functions and the code for all three checks. Next, in Section eleven point four, we look at governance in agile organisations. See you there.

What you need to know

From one-off evaluation to continuous checking

An evaluation such as the ATAM (Section 11.1) is a snapshot. The day after it ends, developers start changing the system, and every change can quietly undo a decision the evaluation relied on: a convenient import across a module boundary, an extra synchronous call on the order path, a library with a known vulnerability. Section 1.7 described the result: erosion, when the architecture is violated, and drift, when it is ignored. Periodic reviews catch some of this, months late. The idea in this section is to check the most important architectural decisions automatically and continuously, in the same way that unit tests check behaviour.

The term comes from Neal Ford, Rebecca Parsons and Patrick Kua's Building Evolutionary Architectures (O'Reilly, 2017), whose second edition, with Pramod Sadalage, appeared in November 2022. They borrowed it from evolutionary computing, where a fitness function scores how close a candidate solution is to the goal so that the algorithm can keep the better variants. In architecture, the goal is the set of characteristics the system must keep while everything else changes.

What a fitness function is

The second edition defines an architectural fitness function as any mechanism that provides an objective integrity assessment of some architectural characteristic or characteristics. Three words in that definition do the work. Any mechanism: a unit test, a static-analysis rule, a load test, a monitor, a chaos experiment or even a scheduled manual review all qualify. Objective: the result must not depend on who runs it; "the code feels well structured" is not a fitness function, "no package in the domain layer imports the web framework" is. Architectural characteristic: the check protects a quality the architecture exists to deliver (performance, availability, modifiability, security, data compatibility), not a business rule, which is the job of ordinary tests.

In practice a useful fitness function has a few more properties. It is traceable to a requirement, ideally a quality attribute scenario whose response measure becomes its threshold (Section 2.3). It has an owner who decides what happens when it fails. It fails loudly, by breaking a build or paging someone, rather than populating a dashboard nobody reads. And its threshold and the reason for it are recorded, usually in an ADR, so that a later team can tell the difference between a rule that protects something important and an arbitrary number.

Section 3.8 drew the important distinction with metrics: a metric describes, a fitness function decides. A coupling metric on a dashboard tells you things are getting worse; a fitness function encodes a decision the team has already made, so breaking it forces a conscious choice: fix the code, or change the rule and record why.

Anatomy of a fitness function

A fitness function links an architectural characteristic to a measure, a threshold, an automated check and an action, with a source scenario, an owner and a recorded rationaleCharacteristicorder latencyMeasurep95, p99 at 100/sThreshold1.5 s and 3 sCheckk6 in pipelineActionfail the buildSource: scenario (Section 2.3)Owner, rationale (ADR) and review date
A fitness function turns a scenario's response measure into a threshold, an automated check and an action, with an owner and a recorded reason.

Categories of fitness function

Ford and his co-authors classify fitness functions along several independent dimensions. The classification is useful less as taxonomy than as a design prompt: asking "should this be atomic or holistic, triggered or continual?" often reveals a better check than the first one that came to mind.

Dimensions of fitness functions

DimensionOptionsWhat it meansExample
ScopeAtomic or holisticAtomic checks one characteristic in one context; holistic checks several characteristics interacting in a shared contextAtomic: no cycles between packages. Holistic: order latency measured with encryption, audit logging and caching all switched on
When it runsTriggered, continual or temporalTriggered runs on an event such as a commit, build or deployment; continual runs all the time against the live system; temporal fires after time passes, as a reminder to revisit somethingTriggered: dependency scan on every build. Continual: SLO burn-rate alert in production. Temporal: fail the build when a pinned framework version is more than 12 months old
Kind of resultStatic or dynamicStatic compares against a fixed threshold; dynamic adjusts the threshold to context, such as scaleStatic: p95 under 1.5 s. Dynamic: p95 may rise to 2 s above 150 orders/s, as agreed for extreme peaks
How it is runAutomated or manualAutomated is preferred; some characteristics, such as legal or usability requirements, still need people, ideally on a scheduleManual: quarterly accessibility review against WCAG 2.2 AA
PlanningIntentional or emergentIntentional checks are designed up front from known requirements; emergent ones are added as the team learns what mattersEmergent: a check on consumer lag added after an incident

Holistic checks deserve attention because qualities interact: security controls add latency, caching complicates data freshness, and an atomic test of each in isolation can pass while the combination fails. Continual checks matter because some characteristics only exist in production: real user latency, real failure modes, real cost. A healthy portfolio combines fast, atomic, triggered checks in the build with a smaller number of slower holistic checks before release and continual checks in production.

Where fitness functions run

Fitness functions run at each stage of the delivery pipeline, from structural checks at build time to continual monitoring and chaos experiments in productionCommitBuildTestCanaryProductionDeveloper runsfast checkslocallyDependency rulesDependency CVEsComplexity limitsCycle checksLoad test p95/p99Schema checksContract testsFailover testError rate andlatency comparedwith the oldversionSLO monitorsSynthetic ordersChaos experimentsCost per ordermanual orautomatedtriggered,atomic, statictriggered,holistictriggered,dynamiccontinual,holisticfailures and drift become new work and new fitness functionsa failed fitness function stops the change or raises an alert
Cheap structural checks run on every build, holistic checks before release, and continual checks in production, with failures feeding back as new work.

Fitness functions across the qualities

Most teams start with structure, because structural checks are cheap, fast and deterministic, then add performance, security, resilience and data checks as the pipeline matures. The table gives typical examples; tools change, the patterns do not.

Examples by quality

QualityFitness functionKindTypical tooling
StructureLayer and dependency rules, no cycles, modules use each other only through public APIs (Section 3.4)Atomic, triggered, staticArchUnit, import-linter, dependency-cruiser, Spring Modulith verification
PerformanceEnd-to-end load test with percentile thresholds; per-service latency budgets in each service's own pipelineHolistic, triggeredk6, Gatling, JMeter, Locust; thresholds that fail the job
SecurityFail the build on dependencies with known high or critical vulnerabilities; no secrets in code; container images only from the approved registryAtomic, triggeredpip-audit, npm audit, OWASP Dependency-Check, Trivy, secret scanners; policy as code (Section 11.4)
ResilienceChaos experiments that kill instances or slow dependencies and check that the steady state holds (Section 9.8)Holistic, triggered or continualToxiproxy, Chaos Toolkit, cloud fault-injection services
DataSchema compatibility checks on events and APIs; migrations rehearsed on production-sized data (Section 7.7)Atomic, triggeredSchema registry compatibility checks, buf breaking, consumer-driven contract tests
Operability and costEvery service exposes health checks and standard metrics; cost per order stays below a targetAtomic or continualPipeline checks on service templates; cost dashboards with alerts

Evolutionary architecture and incremental change

Fitness functions are one half of what Ford, Parsons, Kua and Sadalage call evolutionary architecture: an architecture that supports guided, incremental change across multiple dimensions. Each part of the phrase is a design requirement.

Incremental change has a development side and an operational side. Developers must be able to make small changes safely, which depends on modularity and appropriate coupling (Chapter 3) and on keeping each independently deployable part, the architecture quantum of Section 5.1, small enough to change without coordinating with others. Operations must be able to release small changes frequently and safely, which depends on a deployment pipeline, progressive rollout and fast rollback (Section 10.5). Guided is where fitness functions come in: they define what "fit" means and stop increments from degrading it. Multiple dimensions is a reminder that architecture is not only code structure: data, security, operations, team structure and cost all evolve and all need protecting.

The approach changes how architects think about the future. Instead of predicting every requirement and building flexibility for it in advance, which Section 3.8 showed is expensive and often wrong, the team keeps the system easy to change and protects only the characteristics that matter, deferring other decisions to the last responsible moment (Section 1.6). Critics point out real limits. Not every quality can be checked automatically or cheaply; badly chosen thresholds create noise that teams learn to ignore; and some decisions, such as a database or a cloud provider, remain one-way doors however good the pipeline is. Evolutionary architecture is a discipline for keeping the reversible parts reversible and the important parts protected, not a promise that everything can be changed later.

Worked example

Worked example: three Chapter 2 scenarios as fitness functions

Section 2.8 built a utility tree for the food-delivery platform. The team turns three of its leaves into fitness functions, one triggered and holistic, one a chaos experiment, one structural, after the ATAM in Section 11.1 led to asynchronous payment authorisation.

Scenario 1: order latency (H,H)At the Friday peak of 100 orders/s, 95% of order confirmations return within 1.5 s and 99% within 3 s
Fitness function 1Holistic, triggered: a k6 load test against a production-like environment, nightly and before every release, failing if p95 or p99 exceeds the threshold or more than 0.1% of requests fail. Backed by continual production monitoring of the same percentiles from real traffic
Latency budgetsThe 1.5 s is split into budgets: mobile network and TLS 400 ms, gateway 50 ms, ordering 250 ms, pricing 100 ms, database writes 150 ms, leaving 550 ms of headroom now that card authorisation is off the path. Each service's pipeline runs an atomic component load test against its own budget, so a regression is caught in the service that caused it
Scenario 2: checkout uptime (H,H)When the payment provider times out during the peak, orders are accepted and queued, no confirmed order is lost, and customers are told within 10 s
Fitness function 2Holistic chaos experiment, weekly in staging and quarterly as a production game day with a small blast radius: the provider is made to hang; orders must be accepted as pending within 10 s and all must settle after recovery. Owner: ordering team
Scenario 3: new POS vendor (M,L)A new restaurant point-of-sale vendor is integrated in 5 person-days by adding one adapter
Fitness function 3Atomic, triggered, static: an import-linter contract forbids the restaurant domain and API modules and the ordering package from importing a vendor SDK, and a second contract stops adapters importing each other, so the change stays local. Supplemented by a manual check: the effort of each real vendor integration is recorded and compared with the 5-day target
Recorded with eachSource scenario, threshold, owner, the ADR that explains it, and a review date

Each response measure became a threshold without reinterpretation, which is the payoff of writing scenarios well in the first place.

The modifiability scenario cannot be fully automated: the structural rule protects the conditions for a 5-day change, and the manual measurement checks the outcome.

Latency budgets turn one holistic number into atomic checks that tell the right team about a regression.

import http from 'k6/http';
import { check } from 'k6';

export const options = {
  scenarios: {
    friday_peak: { executor: 'constant-arrival-rate', rate: 100, timeUnit: '1s',
                   duration: '10m', preAllocatedVUs: 300 },   // 100 orders per second
  },
  thresholds: {   // the scenario's response measure, as pass/fail rules
    'http_req_duration{name:place_order}': ['p(95)<1500', 'p(99)<3000'],
    'http_req_failed{name:place_order}': ['rate<0.001'],
  },
};

const order = { restaurantId: 'r-42', items: [{ sku: 'pad-thai', qty: 1 }] };

export default function () {
  const res = http.post(`${__ENV.BASE_URL}/orders`, JSON.stringify(order), {
    headers: { 'Content-Type': 'application/json' }, tags: { name: 'place_order' } });
  check(res, { 'order accepted': (r) => r.status === 201 || r.status === 202 });
}
Fitness function 1: a k6 load test whose thresholds are the scenario's response measure. k6 exits with an error when a threshold fails, which fails the pipeline job.
# Fitness function: checkout keeps accepting orders when payments hang (illustrative)
# Runs weekly in staging; toxiproxy, api and ledger are test fixtures.
def test_orders_survive_payment_provider_outage(toxiproxy, api, ledger):
    toxiproxy.add_latency("payment-provider", latency_ms=30_000)   # provider hangs
    placed = [api.place_order(sample_order()) for _ in range(200)]

    assert all(r.status in (201, 202) for r in placed)            # still accepted
    assert all(r.json()["state"] == "PENDING_PAYMENT" for r in placed)
    assert max(r.elapsed_seconds for r in placed) < 10            # told within 10 s

    toxiproxy.remove_toxics("payment-provider")                    # provider recovers
    ledger.wait_until_settled(timeout_seconds=300)
    assert ledger.lost_orders(placed) == []                        # none lost
Fitness function 2: a chaos experiment written as a test (illustrative; the fixtures wrap a fault-injection proxy, the API and the payments ledger).
# .importlinter: only the POS adapters may use vendor SDKs
[importlinter]
root_package = fooddelivery
include_external_packages = True

[importlinter:contract:pos-vendors-isolated]
name = Vendor POS SDKs are used only inside the POS adapters
type = forbidden
source_modules =
    fooddelivery.restaurant.domain
    fooddelivery.restaurant.api
    fooddelivery.ordering
forbidden_modules =
    tillpro_sdk
    kitchenlink

[importlinter:contract:adapters-independent]
name = POS adapters do not import each other
type = independence
modules =
    fooddelivery.restaurant.pos_adapters.tillpro
    fooddelivery.restaurant.pos_adapters.kitchenlink
Fitness function 3: import-linter contracts that keep vendor-specific code inside the adapters, run with lint-imports on every build.

Data compatibility is protected in the same way. Before a change to the OrderPlaced event can merge, the pipeline asks the schema registry whether the new schema is compatible with the latest registered version under the subject's compatibility rule, and fails if it is not:

# Data fitness function: block the merge if the new event schema would break consumers
curl -s -X POST -H "Content-Type: application/vnd.schemaregistry.v1+json" \
  --data @build/order-placed-v7.request.json \
  "$SCHEMA_REGISTRY_URL/compatibility/subjects/order-placed-value/versions/latest" \
  | jq -e '.is_compatible == true'
A data fitness function using a schema registry's compatibility endpoint; jq -e returns a non-zero exit code, failing the job, when the schema is incompatible.

Designing fitness functions that last

Start small and important. Pick the three to five characteristics that the utility tree rates highest, write one fitness function for each, and make sure they are fast and reliable before adding more. A dozen trusted checks are worth more than a hundred that fail at random.

Use ratchets for existing systems. When a codebase already violates a rule, record the current violations as a baseline and fail only on new ones; tighten the baseline as the team pays the debt down. The same works for performance: fail on regressions of more than an agreed margin against the last release while working towards the scenario's target.

Make thresholds explicit and owned. Keep thresholds in version-controlled configuration next to a reference to the ADR that justifies them, with a named owner. When a fitness function fails, the owner decides between fixing the code and changing the rule, and the decision is recorded. Treat a fitness function that is frequently overridden as a finding: either the rule is wrong or the architecture is under real pressure.

Keep the pipeline fast. Atomic checks belong in every build; heavy holistic checks such as ten-minute load tests run nightly, before release or on the release candidate. Continual checks in production need the same care as any alert: tied to user impact, with a runbook, and silent when nothing is wrong.

Questions people ask

Are fitness functions just tests?

Many are implemented as tests, but their subject is different. Ordinary tests check that features behave correctly; fitness functions check architectural characteristics such as structure, latency, resilience or compatibility. Some, such as production monitors and manual reviews, are not tests at all.

Who writes fitness functions: architects or developers?

Both. Architects usually identify the characteristics and thresholds and often write the first checks, which is a good way to stay hands-on (Section 1.4). Teams own and maintain them like any other code.

How do fitness functions relate to SLOs?

An SLO with an alert on its error budget is a continual, holistic fitness function for availability or latency in production (Section 9.5). Pipeline fitness functions try to catch the same regressions before they reach users.

Check your understanding

What is the difference between a metric and a fitness function?

A metric describes a property; a fitness function encodes a decision with a threshold and an action, so breaking it fails a build or raises an alert and forces a conscious choice.

Give one example each of an atomic and a holistic fitness function.

Atomic: a rule forbidding imports from the web layer into the domain. Holistic: a load test measuring order latency with security, logging and caching all active.

What does a latency budget add to an end-to-end latency fitness function?

It splits the end-to-end target into per-service limits that each service checks in its own pipeline, so a regression is detected in the service that caused it.

What are the three parts of the definition of evolutionary architecture?

It supports guided (by fitness functions), incremental (small, safe changes and releases) change across multiple dimensions (code, data, security, operations, teams and more).

Next · 11.4GovernanceWhat architecture governance is for and how to do it without slow gates: principles, standards, guardrails, the advice process, advisory forums, technology radars, policy as code and enterprise architecture.Continue →

Sources and further reading

Have a question about this section? ArchitectBot, the course’s AI study assistant, answers from the course first.

Ask: “Which three fitness functions should we write first for a service that must confirm orders quickly and survive a payment provider outage?”