ArchUnit: Architecture as Code
Every software project starts with a clean whiteboard diagram: “The Web Layer talks to Service, and Service talks to Data.”
Six months later, the code is a mess. Controllers are calling Repositories directly. Domain objects depend on HTTP libraries. Architecture Drift has set in.
ArchUnit solves this by allowing you to write unit tests that enforce architectural rules. If someone breaks the architecture, the build fails.
1. The Genesis: Architecture as Code
Manual code reviews are not enough. Reviewers get tired. They miss subtle dependency violations. ArchUnit treats your architecture as a testable artifact, just like your business logic.
2. Core Concepts
ArchUnit operates on compiled Java bytecode.
- Importer: Reads class files (
.class) into a graph. - Rules: Fluent API to define constraints (
noClasses()...). - Check: verifying the graph against the rules.
Java Implementation
3. Hardware Reality: Static Analysis
ArchUnit is fast because it uses static analysis (Bytecode inspection) rather than reflection or runtime execution. It doesn’t load classes into the JVM ClassLoader. It parses the bytecode headers to build a dependency graph. This means it can analyze thousands of classes in milliseconds.
4. Common Patterns
Layered Architecture
Enforce the strict flow of control.
layeredArchitecture()
.consideringAllDependencies()
.layer("Web").definedBy("..web..")
.layer("Service").definedBy("..service..")
.layer("Persistence").definedBy("..persistence..")
.whereLayer("Web").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Web")
.whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service");
Cycle Detection
Cyclic dependencies (A → B → A) make code impossible to refactor.
slices().matching("com.myapp.(*)..")
.should().beFreeOfCycles();
5. Interactive: Violation Visualizer
Drag the dependency arrow to see which connections are allowed.
6. Best Practices
- Fail Fast: Run ArchUnit tests as part of your normal unit test suite.
- Common Rules: Use
GeneralCodingRulesto ban standard anti-patterns (e.g.,System.out.println,joda-time). - Naming Conventions: Enforce that all implementations of
Repositorymust end withRepository.