ArchUnit - Checking your own code standarts

Deniz G
3 min readAug 26, 2021

--

While everything is going well at the beginning of the project, many code violations are caused as the use cases become more complex and experienced developers leave and are replaced by new ones.

In order to prevent them, code standards are set to ensure that everyone in the project speaks a common coding language.

Based on these code standards, code reviews are made in certain time periods, and codes are checked, and violations are detected and corrected.

If you develop a web project in Layered Architecture, your sample code standards might be:

  • DTOs should only be accessible from the Controller layer, not Service or Repository layer.
  • If it is necessary to create domain objects from DTOs, it should be done by Mappers.
  • Sample data should be prepared for Controllers, and Swagger documentation should be made.

We have totally 48 code standards including the above rules in server-side development.

Wouldn’t it be good to automate code review as with everything else?

ArchUnit

  • In short, it is not possible to develop beyond the set limits (code standarts) by ArchUnit.
  • Continuity is ensured by automating the code review process. It even provides security benefits by catching missed violations.
  • Code review is included in the Continuous Deployment/Delivery process and the code standards are constantly checked during the build phase. if violation is caught, the build will fail.
  • It also teaches the beginners as the codes they write fail, it accelerates the process of familiarization the code. Thanks to ArchUnit, the beginners can quickly figure out what he needs to do and how.

Let me define the standards I gave as an sample above. The ArchUnit formula is as follows:

<what to control> that $PREDICATE should $CONDITON

  • What to control? class, method, field, architecture…
  • Which? satisfies $PREDICATE
  • How to control? with $CONDITION

ArchUnit uses the Fluent API to define these predicates and conditions. It has many predefined definitions. When it is not enough, we can make our own custom definitions (DescribePredicate for custom predicates, ArchCondition for custom conditions).

Standart-1

@ArchTest
public final ArchRule test3TierArchitecture = Architectures
.layeredArchitecture()
.layer("RestController").definedBy("..controller")
.layer("Service").definedBy("..service")
.layer("Repository").definedBy("..repository")
.layer("DTO").definedBy("..controller.dto")
.whereLayer("DTO").mayOnlyBeAccessedByLayers("RestController")
// ... others
;

ArchUnit rule using completely predefined methods.

Standart-2

private final ArchCondition<JavaClass> dtoHasMappedClass = new ArchCondition<>("DTO mapped by MapStruct") {
@Override
public void check(JavaClass item, ConditionEvents events) {
// ...
}
};
@ArchTest
public final ArchRule testDTOMappedToEntity = ArchRuleDefinition
.classes().that().haveSimpleNameEndingWith("DTO")
.should(this.dtoHasMappedClass)
.because("DTO-Entity conversion must be on MapStruct.");

ArchUnit rule using both predefined and custom methods.

Standart-3

private final DescribedPredicate<JavaMethod> apiMethods = new DescribedPredicate<>("API methods") {
@Override
public boolean apply(JavaMethod input) {
// ...
}
};
private final ArchCondition<JavaMethod> isExampleArgumentProvided = new ArchCondition<>("API paramater must be exampled.") {
@Override
public void check(JavaMethod item, ConditionEvents events) {
// ...
}
};
@ArchTest
public final ArchRule testParameterDocumented = ArchRuleDefinition
.methods().that(this.apiMethods)
.should(this.isExampleArgumentProvided);

ArchUnit rule using completely custom methods.

Sharing ArchUnit Rules

ArchUnit Plugin (https://github.com/societe-generale/arch-unit-maven-plugin) is an easy way to distribute ArchUnit rules across projects. It also enters in build phase. The build will break if any violation is introduced, giving developers very fast feedback on their work.

We defined ArchUnit rules. So how can we share these rules if we are using microservice architecture? Copy-pasting all the rules into every microservice would not be a smart move. Because when there is a change in the rules, the rule in each microservice has to change.

So, it makes more sense to present the rules from a central library such as infrastructure libraries. Each microservice adds this library as a dependency to its ArchUnit plugin. Thus, we prevent manual management of the distribution. We package the rules and distribute them to all projects.

Summary

Just as the functionality of the written codes is verified by Unit, Integration and end-to-end tests, the written code itself should be verified to increase the quality of the codebase. So in short, ArchUnit measures the quality of the code.

--

--