Core Java

Validating code and architecture constraints with ArchUnit

Introduction

ArchUnit is a library for checking Java code against a set of self defined code and architecture constraints. These constraints can be defined in a fluent Java API within unit tests. ArchUnit can be used to validate dependencies between classes or layers, to check for cyclic dependencies and much more. In this post we will create some example rules to see how we can benefit from ArchUnit.

Required dependency

To use ArchUnit we need to add the following dependency to our project:

1
2
3
4
5
6
<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit-junit5</artifactId>
    <version>0.13.0</version>
    <scope>test</scope>
</dependency>

If you are still using JUnit 4 you should use the archunit-junit4 artifact instead.

Creating the first ArchUnit rule

Now we can start creating our first ArchUnit rule. For this we create a new class in our test folder:

01
02
03
04
05
06
07
08
09
10
@RunWith(ArchUnitRunner.class//only for JUnit 4, not needed with JUnit 5
@AnalyzeClasses(packages = "com.mscharhag.archunit")
public class ArchUnitTest {
 
    // verify that classes whose name name ends with "Service" should be located in a "service" package
    @ArchTest
    private final ArchRule services_are_located_in_service_package = classes()
            .that().haveSimpleNameEndingWith("Service")
            .should().resideInAPackage("..service");
}

With @AnalyzeClasses we tell ArchUnit which Java packages should be analyzed. If you are using JUnit 4 you also need to add the ArchUnit JUnit runner.

Inside the class we create a field and annotate it with @ArchTest. This is our first test.

We can define the constraint we want to validate by using ArchUnits fluent Java API. In this example we want to validate that all classes whose name ends with Service (e.g. UserService) are located in a package named service (e.g. foo.bar.service).

Most ArchUnit rules start with a selector that indicates what type of code units should be validated (classes, methods, fields, etc.). Here, we use the static method classes() to select classes. We restrict the selection to a subset of classes using the that() method (here we only select classes whose name ends with Service). With the should() method we define the constraint that should be matched against the selected classes (here: the classes should reside in a service package).

When running this test class all tests annotated with @ArchTest will be executed. The test will fail, if ArchUnits detects service classes outside a service package.

More examples

Let’s look at some more examples.

We can use ArchUnit to make sure that all Logger fields are private, static and final:

1
2
3
4
5
6
7
// verify that logger fields are private, static and final
@ArchTest
private final ArchRule loggers_should_be_private_static_final = fields()
        .that().haveRawType(Logger.class)
        .should().bePrivate()
        .andShould().beStatic()
        .andShould().beFinal();

Here we select fields of type Logger and define multiple constraints in one rule.

Or we can make sure that methods in utility classes have to be static:

1
2
3
4
5
// methods in classes whose name ends with "Util" should be static
@ArchTest
static final ArchRule utility_methods_should_be_static = methods()
        .that().areDeclaredInClassesThat().haveSimpleNameEndingWith("Util")
        .should().beStatic();

To enforce that packages named impl contain no interfaces we can use the following rule:

1
2
3
4
5
// verify that interfaces are not located in implementation packages
@ArchTest
static final ArchRule interfaces_should_not_be_placed_in_impl_packages = noClasses()
        .that().resideInAPackage("..impl..")
        .should().beInterfaces();

Note that we use noClasses() instead of classes() to negate the should constraint.

(Personally I think this rule would be much easier to read if we could define the rule as interfaces().should().notResideInAPackage(“..impl..”). Unfortunately ArchUnit provides no interfaces() method)

Or maybe we are using the Java Persistence API and want to make sure that EntityManager is only used in repository classes:

1
2
3
4
@ArchTest
static final ArchRule only_repositories_should_use_entityManager = noClasses()
        .that().resideOutsideOfPackage("..repository")
        .should().dependOnClassesThat().areAssignableTo(EntityManager.class);

Layered architecture example

ArchUnit also comes with some utilities to validate specific architecture styles.

For example can we use layeredArchitecture() to validate access rules for layers in a layered architecture:

1
2
3
4
5
6
7
8
@ArchTest
static final ArchRule layer_dependencies_are_respected = layeredArchitecture()
        .layer("Controllers").definedBy("com.mscharhag.archunit.layers.controller..")
        .layer("Services").definedBy("com.mscharhag.archunit.layers.service..")
        .layer("Repositories").definedBy("com.mscharhag.archunit.layers.repository..")
        .whereLayer("Controllers").mayNotBeAccessedByAnyLayer()
        .whereLayer("Services").mayOnlyBeAccessedByLayers("Controllers")
        .whereLayer("Repositories").mayOnlyBeAccessedByLayers("Services");

Here we define three layers: Controllers, Services and Repositories. The repository layer may only accessed by the service layer while the service layer may only be accessed by controllers.

Shortcuts for common rules

To avoid that we have to define all rules our self, ArchUnit comes with a set of common rules defined as static constants. If these rules fit our needs, we can simply assign them to @ArchTest fields in our test.

For example we can use the predefined NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS rule if we make sure no exceptions of type Exception and RuntimeException are thrown:

1
2
@ArchTest
private final ArchRule no_generic_exceptions = NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS;

Summary

ArchUnit is a powerful tool to validate a code base against a set of self defined rules. Some of the examples we have seen are also reported by common static code analysis tools like FindBugs or SonarQube. However, these tools are typically harder to extend with your own project specific rules and this is where ArchUnit comes in.

As always you can find the Sources from the examples on GitHub. If you are interested in ArchUnit you should also check the comprehensive user guide.

Published on Java Code Geeks with permission by Michael Scharhag, partner at our JCG program. See the original article here: Validating code and architecture constraints with ArchUnit

Opinions expressed by Java Code Geeks contributors are their own.

Michael Scharhag

Michael Scharhag is a Java Developer, Blogger and technology enthusiast. Particularly interested in Java related technologies including Java EE, Spring, Groovy and Grails.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Elena Gillbert
4 years ago

Hi… A common problem faced by development organizations is that code implementations can often diverge from the original design and architecture. The problem is common enough, especially on large projects, that a new tool has emerged to help test that a code implementation is consistent with the originally defined architecture. ArchUnit is a small, simple, extensible, open source Java testing library for verifying predefined application architecture characteristics and architectural constraints. An ArchUnit test is written and runs as a unit test that gives developers and application architects fast feedback on their work. It guarantees that a software build will break… Read more »

Andriy
4 years ago

Whether any advantages ArchUnit over static code analysis tools like CheckStyle or PMD? Anyway, thank you for the article!

architects auckland
4 years ago

An amazing article. It’s nice to read a quality blog post. “Java Code Geeks”. I think you made some good points in this post.

Builders Rotorua
4 years ago

Hey there, first of all, thank you so much for this post and honestly, I was searching for the same information from the last few days. “Molding with mud Eugene Pandala’s tryst with sustainable architecture”. Keep posting and keep sharing.

Builder Nelson
4 years ago

It’s so amazing and interesting post. Constraints can be defined in a fluent Java API within unit tests. I am very impressed by your blog. It’s really a useful post for all. Thanks for sharing this. You can see https://gardinerbuildingcontractors.co.nz/

Back to top button