Architecture fitness functions turn important architecture properties into repeatable observations with explicit pass, warn, or fail decisions. They do not automate architecture itself. They automate the evidence that a chosen property still holds: domain code does not import infrastructure, public endpoints meet an authentication policy, a service stays inside its latency objective, or a workload’s idle cost remains within an approved envelope.
A useful function connects a principle to a measurable condition, a trusted data source, an execution point, an owner, and an exception path. Without those elements, a slogan such as services must be loosely coupled invites inconsistent interpretation. With them, a team can see a dependency cycle in a pull request, understand why it matters, and either correct it or obtain a time-bounded exception.
Select architecture properties worth continuously testing
Start from decisions with meaningful failure cost and a machine-observable signal. Dependency direction, forbidden libraries, API compatibility, encryption settings, ownership metadata, error budgets, recovery tests, and unit-cost thresholds are candidates. Principles that depend on context or design quality may remain review prompts. Good user experience and use appropriate technology are too ambiguous to become binary gates without first defining a narrower outcome.
Write each function as a small contract: because a quality attribute matters in a stated scope, measure a named property from a named source at a stated cadence; compare it with a threshold; route failure to an owner; and define evidence for closure. This format exposes missing telemetry and prevents a tool from becoming the de facto author of policy.
Classify functions by signal and response time
| Function type | Example | Execution point | Typical response |
|---|---|---|---|
| Static code | No adapter package is imported by domain packages | Local test and pull request | Block new violation |
| Configuration policy | Public storage must deny anonymous access | Infrastructure plan and admission | Block or require approved exception |
| Contract | Provider remains compatible with supported consumers | Provider build and deployment gate | Stop incompatible release |
| Runtime | Checkout latency and error rate stay inside objective | Continuous telemetry window | Alert, limit exposure, or roll back |
| Resilience | Restore completes within recovery objective with valid data | Scheduled exercise | Create corrective action and retest |
| Economic | Cost per successful transaction remains within envelope | Daily or weekly analytical job | Investigate drift before budget breach |
Fast deterministic checks belong close to the developer. Slow, noisy, or statistical checks belong later and should rarely block a commit. A single green pipeline cannot prove runtime resilience; a single transient latency breach should not reject source code. Place every function where its evidence is mature enough to support the consequence.
Design the measurable contract and data quality guard
Specify scope precisely. Instead of no cycles, state no cycles among production packages matching the module namespace, excluding generated clients, with an empty match treated as failure. ArchUnit’s user guide supports package and class dependency checks, layered and onion architecture rules, slice cycle detection, and configurable failure when no classes match. The empty-match guard matters because a renamed package can otherwise make a rule pass without evaluating anything.
For policy as code, version the policy and its test inputs together. Open Policy Agent separates policy decision-making from enforcement and evaluates structured input. Its policy testing guide shows that tests are discovered by naming convention and documents --fail-on-empty, a valuable CI safeguard. Test allowed, denied, malformed, missing, and boundary inputs; do not validate only the happy path.
Implement a thin vertical slice
Choose one static property and one runtime property for a single service. For example, prevent controllers from calling database adapters directly and monitor the percentage of payment requests that finish within the service objective. Add rule identifiers, rationale, owner, introduced date, severity, and remediation link. Emit a machine-readable result and a concise human explanation containing the violating dependency or time series, not merely architecture check failed.
undefined
Run the static rule locally and in CI. Evaluate the runtime rule over a window that reflects the objective, and make deployment automation consume a stable status rather than a raw metric point. AWS Prescriptive Guidance describes automated static analysis as early detection integrated into delivery; that benefit depends on actionable output and a rule set small enough for teams to trust.
Govern severity, rollout, and exceptions
| Result | Meaning | Pipeline behavior | Required record |
|---|---|---|---|
| Pass | Property observed within policy | Continue | Evidence retained with build or window |
| Warn | Risk exists but immediate block is disproportionate | Continue with visible owner notification | Issue, due date, and trend |
| Fail-new | Change introduces or worsens a violation | Block merge or deployment | Violation details or approved exception |
| Fail-system | Current production state exceeds a critical boundary | Limit rollout or initiate response | Incident or corrective action |
| Unknown | Input missing, stale, empty, or tool failed | Fail closed for critical controls; otherwise escalate | Data-quality event and accountable decision |
Roll out new functions in observe-only mode, compare findings with expert review, fix false positives, then enable enforcement for new violations. A baseline can prevent existing debt from blocking all work, but every tolerated violation needs an owner and reduction plan. Exceptions should cite the rule, scope, reason, compensating control, approver, expiry, and test for removal. Permanent ignore lists silently rewrite the architecture.
Review the portfolio quarterly and after incidents or major topology changes. Retire functions whose decisions no longer apply, split functions that combine unrelated properties, and challenge thresholds that never fire. OPA’s CI/CD guidance illustrates policy checks over repository and configuration data; the operating lesson is broader: enforcement must remain connected to current inputs and current authority.
Measure governance effectiveness, not rule volume
Useful measures include violations prevented before merge, median time to remediate, exception age, false-positive rate, unowned rules, unknown results, and incidents linked to an untested property. Count how often a function changes a decision. Hundreds of checks that teams routinely bypass are weaker governance than a focused portfolio covering the system’s most expensive failure modes.
Test the fitness-function infrastructure itself. Seed known violations into representative fixtures, verify that each execution environment loads the expected scope, and alert when result volume unexpectedly drops to zero. Pin parser and policy-engine versions, preserve rule output with the build, and make upgrades run old and new evaluators side by side. A silent change in bytecode analysis, infrastructure-plan JSON, or metric labels can invalidate governance while every displayed result remains green.
Separate control ownership from tool administration. A platform team may run OPA, CI plugins, and telemetry storage; a security or architecture owner defines the policy; service teams supply system context and remediate. Changes to critical thresholds should require the same evidence and review as the architecture decision they enforce. Record policy version in every result so an auditor can reconstruct what was evaluated at release time.
Fitness functions can conflict. A rule minimizing cross-zone calls may oppose a resilience rule requiring zone redundancy; a strict cost ceiling may oppose a latency objective. Do not hide this tension in precedence code. Name the competing quality attributes, define which applies under each workload class, and create a higher-level decision for exceptions. Architecture remains a tradeoff system even when its observations are executable.
Finally, design remediation before enforcement. A dependency violation needs the forbidden edge and an approved alternative; a runtime objective breach needs the affected operation, window, release, and rollback path; a cost breach needs allocation and demand context. A function that only says no pushes architecture work into private chats and encourages bypasses. Good feedback shortens the route from detected drift to a verified correction.
Key takeaways
- Translate a principle into scope, signal, threshold, cadence, owner, consequence, and exception path.
- Use static, policy, contract, runtime, resilience, and economic functions at the point where their evidence is reliable.
- Treat missing or empty input as an explicit result, especially for critical controls.
- Introduce enforcement gradually and prevent new debt before attempting to eliminate every legacy violation.
- Evaluate the portfolio by prevented risk, remediation, exceptions, and false positives rather than number of rules.
Frequently asked questions
Are fitness functions just architecture unit tests?
Architecture tests are one type. Fitness functions also observe deployment configuration, API compatibility, live service objectives, recovery exercises, security posture, and cost. The common feature is an explicit architecture property evaluated repeatedly.
Should every failed fitness function block delivery?
No. Blocking is appropriate for deterministic, high-confidence checks where the change causes unacceptable risk. Statistical runtime signals, advisory cost drift, and newly introduced rules often need warning, controlled rollout, or human review. Consequence should match evidence quality and impact.
Who owns an architecture fitness function?
A team that can interpret the property and change either the system or policy must own it. A central platform may provide tooling and templates, but an unowned threshold will become stale. Cross-cutting rules need a named governance owner and service teams responsible for remediation.
Conclusion
Executable architecture governance works when a small number of consequential properties become dependable feedback. Define the decision first, choose a trustworthy signal, test the test, make failures actionable, and preserve a governed route for context. Fitness functions then support decentralized delivery without pretending that architecture can be reduced to a dashboard of green checks.