

ArchUnit Verifies Architecture Rules for Java Applications
source link: https://www.infoq.com/news/2022/10/archunit/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ArchUnit Verifies Architecture Rules for Java Applications
Oct 17, 2022 3 min read
ArchUnit allows developers to enforce architecture rules such as naming conventions, class access to other classes, and the prevention of cycles. The library was originally created in 2017 by Peter Gafert and version 1.0.0 was released in October.
ArchUnit works with all Java test frameworks and offers specific dependencies for JUnit. The following dependency should be used for JUnit 5:
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
Now the ClassFileImporter
can be used to import Java bytecode into Java code. For example, to import all classes in the org.example package:
JavaClasses javaClasses = new ClassFileImporter().importPackages("org.example");
Now the ArchRule
class may be used to define architectural rules for the imported Java classes in a Domain Specific Language (DSL). There are various types of checks available, the first one is for package dependencies. The check specifies that no classes inside repository packages should use classes inside controller packages:
ArchRule rule = noClasses()
.that().resideInAPackage("..repository..")
.should().dependOnClassesThat().resideInAPackage("..controller..");
Two classes are used to verify the rules, a CourseController
class inside a controller package and a CourseRepository
class inside a repository package:
public class CourseController {
private CourseRepository courseRepository;
}
public class CourseRepository {
CourseController courseController;
}
This is not allowed by the ArchRule
defined before, which can be tested automatically with JUnit:
AssertionError assertionError =
Assertions.assertThrows(AssertionError.class, () -> {
rule.check(javaClasses);
});
String expectedMessage = """
Architecture Violation [Priority: MEDIUM] -
Rule 'no classes that reside in a package
'..repository..' should depend on classes that reside in a package
'..controller..'' was violated (1 times):
Field <org.example.repository.CourseRepository.courseController> has type
<org.example.controller.CourseController> in (CourseRepository.java:0)""";
assertEquals(expectedMessage, assertionError.getMessage());
The CourseController
and CourseRepository
depend on each other, which often is a design flaw. The cycle check detects cycles between classes and packages:
ArchRule rule = slices()
.matching("org.example.(*)..")
.should().beFreeOfCycles();
AssertionError assertionError =
Assertions.assertThrows(AssertionError.class, () -> {
rule.check(javaClasses);
});
String expectedMessage = """
Architecture Violation [Priority: MEDIUM] - Rule 'slices matching
'org.example.(*)..' should be free of cycles' was violated (1 times):
Cycle detected: Slice controller ->\s
Slice repository ->\s
Slice controller
1. Dependencies of Slice controller
- Field <org.example.controller.CourseController.courseRepository> has type
<org.example.repository.CourseRepository> in (CourseController.java:0)
2. Dependencies of Slice repository
- Field <org.example.repository.CourseRepository.courseController> has type
<org.example.controller.CourseController> in (CourseRepository.java:0)""";
assertEquals(expectedMessage, assertionError.getMessage());
Class and Package containment checks allow the verification of naming and location conventions. For example, to verify that no interfaces are placed inside implementation packages:
noClasses()
.that().resideInAPackage("..implementation..")
.should().beInterfaces().check(classes);
Or to verify that all interfaces have a name containing "Interface":
noClasses()
.that().areInterfaces()
.should().haveSimpleNameContaining("Interface").check(classes);
These containment checks may be combined with an annotation check. For example, to verify that all classes in the controller package with a RestController
annotation have a name ending with Controller:
classes()
.that().resideInAPackage("..controller..")
.and().areAnnotatedWith(RestController.class)
.should().haveSimpleNameEndingWith("Controller");
Inheritance checks allow, for example, to verify that all classes implementing the Repository
interface have a name ending with Repository:
classes().that().implement(Repository.class)
.should().haveSimpleNameEndingWith("Repository")
With the layer checks, it's possible to define the architecture layers of an application and then define the rules between the layers:
Architectures.LayeredArchitecture rule = layeredArchitecture()
.consideringAllDependencies()
// Define layers
.layer("Controller").definedBy("..controller..")
.layer("Repository").definedBy("..Repository..")
// Add constraints
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Repository").mayOnlyBeAccessedByLayers("Controller");
AssertionError assertionError =
Assertions.assertThrows(AssertionError.class, () -> {
rule.check(javaClasses);
});
String expectedMessage = """
Architecture Violation [Priority: MEDIUM] - Rule 'Layered architecture
considering all dependencies, consisting of
layer 'Controller' ('..controller..')
layer 'Repository' ('..Repository..')
where layer 'Controller' may not be accessed by any layer
where layer 'Repository' may only be accessed by layers ['Controller']'
was violated (2 times):
Field <org.example.repository.CourseRepository.courseController> has type
<org.example.controller.CourseController> in (CourseRepository.java:0)
Layer 'Repository' is empty""";
assertEquals(expectedMessage, assertionError.getMessage());
More information can be found in the extensive user guide and official examples from ArchUnit are available on GitHub.
About the Author
Johan Janssen
Architect at ASML, loves to share knowledge mainly around Java. Spoke at conferences such as Devoxx, Oracle Code One, Devnexus, and many more. Assisted conferences by participating in program committees and invented and organized JVMCON. Received the JavaOne Rock Star and Oracle Code One Star awards. Wrote various articles both for digital and printed media. Maintainer of various Java JDK/JRE packages for Chocolatey with around 100 thousand downloads a month.
Show moreRecommend
-
15
Code Review总是让人又爱又恨,它可以帮助我们在提测之前发现很多代码中比较“丢人”的问题,但是,Code Review通常会比写代码更加耗费精力,因为你需要理解别人的代码,而为了这一目的,往往需要很多次的沟通。 人们常...
-
10
SivaLabsMy Experiments on TechnologyShare this:While building the software we all agree, as a team, to follow a set of guidelines which are typ...
-
7
Slitter: a slab allocator that trusts, but verifies (in Rust, for C)Paul Khuong · August 4, 2021Slitter is Backtrace’s deliberately middle-of-the-road
-
11
PC games that run great on the Steam Deck will get a special ‘Verified’ check mark Steam Deck owners can see which games have been tested with the portable gaming PC
-
3
How Cloudflare verifies the code WhatsApp Web serves to users 03/10/2022
-
7
Improving Java’s Visibility Modifiers with ArchUnitPublished February 23, 2021Encapsulation and separation of internal components from public ones is probably on...
-
8
Instagram is testing an AI tool that verifies your age by scanning your face You can try it out yourself — how old does the computer think you are? ...
-
5
AdvertisementCloseTech Policy
-
6
A Swiss company says it has pulled CO2 out of the atmosphere and stored it underground / Climeworks says a third-party auditor has verified its carbon removal for Microsoft, Stripe, and Shopify.
-
2
Enforcing Your Architecture with ArchUnit Table Of Contents ArchUnit is a Java library to validate your software architecture. The libra...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK