Architecture fitness functions are automated tests that verify architectural constraints are not violated as a codebase evolves. Embedded in CI/CD pipelines, they prevent the gradual erosion of architectural integrity that plagues long-lived systems β providing the same safety net for architecture that unit tests provide for business logic.
What Are Architecture Fitness Functions?
The term "fitness function" comes from evolutionary architecture, introduced by Neal Ford, Rebecca Parsons, and Patrick Kua in "Building Evolutionary Architectures" (O'Reilly). A fitness function is any mechanism that evaluates how well the system conforms to a desired architectural property β dependency structure, performance characteristics, security posture, code complexity, or compliance with naming and layering conventions.
The power of fitness functions is that they make implicit architectural rules explicit and enforceable. "The service layer should not depend on the presentation layer" is a common architectural rule that lives in architecture documentation but is routinely violated as codebases grow. A fitness function turns that rule into a failing test that blocks deployment.
Categories of Architecture Fitness Functions
Tooling for Architecture Fitness Functions
| Tool | Language/Ecosystem | Primary Use Case |
|---|---|---|
| ArchUnit | Java / Kotlin | Package dependency rules, layer constraints, naming conventions |
| NetArchTest | .NET / C# | Namespace dependency rules, DDD layer enforcement |
| Dependency Cruiser | JavaScript / TypeScript | Module dependency graph rules, circular dependency detection |
| ESLint import/no-restricted-imports | JavaScript / TypeScript | Import restriction rules enforced at lint time |
| SonarQube / SonarCloud | Multi-language | Complexity metrics, technical debt thresholds, code smell limits |
| Spectral | API / OpenAPI | API design rule enforcement (naming, versioning, response formats) |
| Pact | Multi-language | Consumer-driven contract testing for microservices |
| Bundlesize / size-limit | JavaScript | Bundle size budget enforcement |
ArchUnit Example: Enforcing Layered Architecture
ArchUnit is the most widely adopted fitness function tool for Java systems. It allows you to write architectural rules as JUnit tests:
@ArchTest
static final ArchRule domainMustNotDependOnInfrastructure =
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat()
.resideInAPackage("..infrastructure..");
@ArchTest
static final ArchRule servicesMustNotAccessRepositoriesDirectly =
noClasses().that().resideInAPackage("..service..")
.should().accessClassesThat()
.areAnnotatedWith(Repository.class)
.because("services should use repositories through interfaces only");
@ArchTest
static final ArchRule noCircularDependencies =
slices().matching("com.example.(*)..").should().beFreeOfCycles();
These tests run as part of the standard Maven/Gradle test suite and fail the build if violations are introduced, preventing architectural drift before it reaches production.
Dependency Cruiser for TypeScript/JavaScript
For TypeScript projects, Dependency Cruiser provides equivalent capabilities through a rules configuration file:
// .dependency-cruiser.cjs
module.exports = {
forbidden: [
{
name: "no-circular",
severity: "error",
comment: "Circular dependencies are forbidden",
from: {},
to: { circular: true }
},
{
name: "domain-no-infra",
severity: "error",
from: { path: "^src/domain" },
to: { path: "^src/infrastructure" }
}
]
};