The architecture of a system is usually a design that was made at a certain moment in time, and that fit the bill for the requirements at that time. However, as the system evolves and developers come and go, it’s hard to keep everyone up to date on a reference architecture or encourage developers to make changes to it, since it’s usually in the form of a set of documents, confluence pages, or something similar. This can result in code that violates architectural boundaries, piling up as technical debt.
ArchUnit, an architecture testing library for Java, enables you to formalize your architectural constraints as a set of ordinary unit tests. Using a fluent API, it enables you to formalize your architectural constraints in a descriptive way and run these constraints as a part of your build pipeline. Because ArchUnit tests are normal unit tests, they can evolve as your architecture changes, using version control to keep track of all changes.
In this presentation, lasting approximately 25 minutes, we’ll explore the ArchUnit library, and show you how you can use it to enforce your architectural constraints. A live demo is included.
We’ll cover the following topics:
• Getting started with ArchUnit
• Different checks (package dependencies, layers, inheritance, annotations) supported by ArchUnit
• Junit 4 and 5 support
• Rules as a separate module
8. Software architectural constraints as unit tests
• Normal unit tests => versioning
• Fluent API
• Support for JUnit 4 and JUnit 5
• Extensible with custom rules
• Open source
Architectural rules across boundaries out of scope
https://github.com/TNG/ArchUnit
11. api
core
client
@AnalyzeClasses(packages = "nl.craftsmen.archunitdemo")
public class ArchUnitJunit5Test {
@ArchTest
public static final ArchRule packageRule = noClasses()
.that()
.resideInAPackage("..core..")
.should()
.dependOnClassesThat()
.resideInAnyPackage("..api..", "..client...");
}
Package dependency checks
No test methods
wildcard
15. api
core
client
@ArchTest
public static final ArchRule layerRule =
Architectures.layeredArchitecture()
.layer("Api").definedBy("..api..")
.layer("Core").definedBy("..core..")
.layer("Client").definedBy("..client..")
.whereLayer("Api")
.mayNotBeAccessedByAnyLayer()
.whereLayer("Core").
.mayOnlyBeAccessedByLayers("Api", "Client")
.whereLayer("Client")
.mayNotBeAccessedByAnyLayer();
Layer checks
16. Onion architecture
public static final ArchRule onionRule = onionArchitecture()
.domainModels("..core..")
.domainServices("..core..")
.applicationServices("..core..")
.adapter("client", "..client..")
.adapter("api", "..api..");
17. public static ArchCondition<JavaClass> notHaveFieldsAnnotatedWithDeprecated =
new ArchCondition<JavaClass>("should not have field annotated with
@Deprecated") {
@Override
public void check(JavaClass item, ConditionEvents events) {
//if class has a deprecated field
events.add(SimpleConditionEvent.violated(item, "@Deprecated not
allowed"));
}
};
@ArchTest
public static final ArchRule customRule = noClasses().should(notHaveFieldsAnnotatedWithDeprecated);
Custom rules
20. Summary
• Nice addition to your project to enforce software
architecture constraints
• Unit test format, so easily runnable, versionable
• Make a separate artifact to share between microservices