When you create a microservice or a library, you start defining the location of each group of elements (enums, classes, interfaces) in one or more packages and the best practices, i.e. if you use Spring Boot the controllers should be annotated with @RestController. At the beginning, everything will be fine because a little group of developers works on the project. However, when you start adding developers of your team or from another team in the company add new functionalities is when the error appears.
To prevent any error in the code of the project, someone in your team needs to review all the changes before merging in master. This approach looks good at the beginning but there are some problems:
- Really time-consuming tasks
- The rules could change in the future so everyone needs to know the new rules before checking any code.
At the back of your mind, the first solution would be tools like Checkstyle, PMD, or Findbugs which does a statical analysis of the code but these tools do not have the possibility to check the structure of the project.
Some years ago Archunit appeared which is a free and extensible library for checking the architecture of your project using just a unit test. This library gives you the chance to check the layer’s structure (which package/class depends on another), valid annotations in each class, check for cyclic dependencies, and more.
To start your Archunit tests you will need to add the dependency related to the version of Junit that you use.
<dependency> <groupId>com.tngtech.archunit</groupId> <artifactId>archunit-junitX</artifactId> //"X" could be 4/5 <version>0.15.0</version> <scope>test</scope> </dependency>
After adding the dependency your first Archunit test should look like the example below:
Let me explain in detail each block of code:
- With this annotation, you tell Archunit which package needs to validate all the rules, a good practice is to put the package which contains all the objects of your project. Also, practice is telling to Archunit not to validate the test just the code that does something.
- You need to indicate the field or method is an Archunit Test.
- This is the test with all the conditions to validate. The example checks that all the fields inside of any class in this particular package aren’t public. Just to clarify, “fields()” is a static import of “ com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields”
In this post, you can see some examples of which things you can validate with Archunit.
Archunit defines some general rules of coding you can use to validate your project, most of them are defined in the class “GeneralCodingRules”. To use these rules you need to add a test to your project, some examples of these rules are:
You can validate all the objects in one particular package contain a suffix, i.e. xxxController or xxxService.
Good architecture needs to have different layers and each of them will only be able to access some other particular layers. You can define the layers and the relation between them, i.e. the controllers in one microservices can not be accessed for any other layer.
In the controller’s endpoints, you can validate the type of response. For example in the case of Spring Boot, a good practice is to return a “ReponseEntity”.
Sometimes you need to validate that some classes/interfaces/methods or, fields use a particular annotation.
There are some cases when the methods to validate some rules are not cover with Archunit by default so you can create custom conditions like validating that your entities/model objects contain “equals” and “hashcode” methods.
To use this condition in one particular test you can do this:
There are some good practices in order to reduce the lines of code and organize the test depending on the types of rules. Here are some of them:
- Define the package of the classes to analyze, the name of the packages or any constant in one class to have a place with all the constants you use in all the tests.
- Define the rules in a way you can reuse them in different places, i.e. you can create a rule that checks that private methods are not allowed and call these methods from different places.
- Try to put all the rules related to one validation group in one class, i.e. create a class that contains all the rules related to the controller validations in the case of one microservice.
- Split each test class to find all the validations related to classes, fields, constructors, and methods in a simpler way. The important thing at this point is to be able to identify each group easier and to be able to prevent the same tests to be checked in a different way.
Archunit is a powerful library that gives you the chance to validate every rule you can imagine in your architecture and reduce the number of errors in your projects.
You need to understand if you add Archunit into an existing project, you would find some issues because most of your projects have some mistakes related to the structure of the names of the classes. It’s key to add the general rules at the beginning and then add the specific ones.
For curious ones, here is the code on Github: