ArchUnit permits builders to implement structure guidelines similar to naming conventions, class entry to different lessons, and the prevention of cycles. The library was initially created in 2017 by Peter Gafert and model 1.0.0 was released in October.
ArchUnit works with all Java check frameworks and gives particular dependencies for JUnit. The next dependency needs to be used for JUnit 5:
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<model>1.0.0</model>
<scope>check</scope>
</dependency>
Now the ClassFileImporter
can be utilized to import Java bytecode into Java code. For instance, to import all lessons within the org.instance bundle:
JavaClasses javaClasses = new ClassFileImporter().importPackages("org.instance");
Now the ArchRule
class could also be used to outline architectural guidelines for the imported Java lessons in a Area Particular Language (DSL). There are numerous forms of checks obtainable, the primary one is for bundle dependencies. The verify specifies that no lessons inside repository packages ought to use lessons inside controller packages:
ArchRule rule = noClasses()
.that().resideInAPackage("..repository..")
.ought to().dependOnClassesThat().resideInAPackage("..controller..");
Two lessons are used to confirm the foundations, a CourseController
class inside a controller bundle and a CourseRepository
class inside a repository bundle:
public class CourseController
non-public CourseRepository courseRepository;
public class CourseRepository
CourseController courseController;
This isn’t allowed by the ArchRule
outlined earlier than, which might be examined robotically with JUnit:
AssertionError assertionError =
Assertions.assertThrows(AssertionError.class, () ->
rule.verify(javaClasses);
);
String expectedMessage = """
Structure Violation [Priority: MEDIUM] -
Rule 'no lessons that reside in a bundle
'..repository..' ought to rely on lessons that reside in a bundle
'..controller..'' was violated (1 instances):
Subject <org.instance.repository.CourseRepository.courseController> has sort
<org.instance.controller.CourseController> in (CourseRepository.java:0)""";
assertEquals(expectedMessage, assertionError.getMessage());
The CourseController
and CourseRepository
rely on one another, which regularly is a design flaw. The cycle verify detects cycles between lessons and packages:
ArchRule rule = slices()
.matching("org.instance.(*)..")
.ought to().beFreeOfCycles();
AssertionError assertionError =
Assertions.assertThrows(AssertionError.class, () ->
rule.verify(javaClasses);
);
String expectedMessage = """
Structure Violation [Priority: MEDIUM] - Rule 'slices matching
'org.instance.(*)..' needs to be freed from cycles' was violated (1 instances):
Cycle detected: Slice controller ->s
Slice repository ->s
Slice controller
1. Dependencies of Slice controller
- Subject <org.instance.controller.CourseController.courseRepository> has sort
<org.instance.repository.CourseRepository> in (CourseController.java:0)
2. Dependencies of Slice repository
- Subject <org.instance.repository.CourseRepository.courseController> has sort
<org.instance.controller.CourseController> in (CourseRepository.java:0)""";
assertEquals(expectedMessage, assertionError.getMessage());
Class and Bundle containment checks permit the verification of naming and site conventions. For instance, to confirm that no interfaces are positioned inside implementation packages:
noClasses()
.that().resideInAPackage("..implementation..")
.ought to().beInterfaces().verify(lessons);
Or to confirm that each one interfaces have a reputation containing “Interface”:
noClasses()
.that().areInterfaces()
.ought to().haveSimpleNameContaining("Interface").verify(lessons);
These containment checks could also be mixed with an annotation verify. For instance, to confirm that each one lessons within the controller bundle with a RestController
annotation have a reputation ending with Controller:
lessons()
.that().resideInAPackage("..controller..")
.and().areAnnotatedWith(RestController.class)
.ought to().haveSimpleNameEndingWith("Controller");
Inheritance checks permit, for instance, to confirm that each one lessons implementing the Repository
interface have a reputation ending with Repository:
lessons().that().implement(Repository.class)
.ought to().haveSimpleNameEndingWith("Repository")
With the layer checks, it is doable to outline the structure layers of an utility after which outline the foundations between the layers:
Architectures.LayeredArchitecture rule = layeredArchitecture()
.consideringAllDependencies()
// Outline 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.verify(javaClasses);
);
String expectedMessage = """
Structure Violation [Priority: MEDIUM] - Rule 'Layered structure
contemplating all dependencies, consisting of
layer 'Controller' ('..controller..')
layer 'Repository' ('..Repository..')
the place layer 'Controller' might not be accessed by any layer
the place layer 'Repository' could solely be accessed by layers ['Controller']'
was violated (2 instances):
Subject <org.instance.repository.CourseRepository.courseController> has sort
<org.instance.controller.CourseController> in (CourseRepository.java:0)
Layer 'Repository' is empty""";
assertEquals(expectedMessage, assertionError.getMessage());
Extra data might be discovered within the intensive user guide and official examples from ArchUnit can be found on GitHub.