ArchUnit permits builders to implement structure guidelines reminiscent of naming conventions, class entry to different courses, 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 take a look at frameworks and provides particular dependencies for JUnit. The next dependency ought to be used for JUnit 5:
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<model>1.0.0</model>
<scope>take a look at</scope>
</dependency>
Now the ClassFileImporter
can be utilized to import Java bytecode into Java code. For instance, to import all courses within the org.instance package deal:
JavaClasses javaClasses = new ClassFileImporter().importPackages("org.instance");
Now the ArchRule
class could also be used to outline architectural guidelines for the imported Java courses in a Area Particular Language (DSL). There are numerous varieties of checks accessible, the primary one is for package deal dependencies. The verify specifies that no courses inside repository packages ought to use courses inside controller packages:
ArchRule rule = noClasses()
.that().resideInAPackage("..repository..")
.ought to().dependOnClassesThat().resideInAPackage("..controller..");
Two courses are used to confirm the foundations, a CourseController
class inside a controller package deal and a CourseRepository
class inside a repository package deal:
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 routinely with JUnit:
AssertionError assertionError =
Assertions.assertThrows(AssertionError.class, () ->
rule.verify(javaClasses);
);
String expectedMessage = """
Structure Violation [Priority: MEDIUM] -
Rule 'no courses that reside in a package deal
'..repository..' ought to rely on courses that reside in a package deal
'..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 courses 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.(*)..' ought 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 Package deal containment checks enable 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(courses);
Or to confirm that each one interfaces have a reputation containing “Interface”:
noClasses()
.that().areInterfaces()
.ought to().haveSimpleNameContaining("Interface").verify(courses);
These containment checks could also be mixed with an annotation verify. For instance, to confirm that each one courses within the controller package deal with a RestController
annotation have a reputation ending with Controller:
courses()
.that().resideInAPackage("..controller..")
.and().areAnnotatedWith(RestController.class)
.ought to().haveSimpleNameEndingWith("Controller");
Inheritance checks enable, for instance, to confirm that each one courses implementing the Repository
interface have a reputation ending with Repository:
courses().that().implement(Repository.class)
.ought to().haveSimpleNameEndingWith("Repository")
With the layer checks, it is attainable to outline the structure layers of an software 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' will 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 info might be discovered within the in depth user guide and official examples from ArchUnit can be found on GitHub.