Static code analysis is important, and there exist plenty of great tools out there like FindBugs, PMD or Detekt.
Unfortunately, all those rules have their limit, they are all not analyzing the whole code base, but only class files in an isolated way.
This is where the great library ArchUnit comes into
the game. This library uses ArchUnit and provides
a collection of ArchRule
s which we consider best-practice
at @cloudflightio.
Those are grouped in:
- Extended Rules for the JDK
- Java Logging
- Java Persistence Framework (JPA)
- Spring Framework (including Transcations & Spring Data)
- Spring Boot
Using this library is simple. First add it as dependency to your test-scope, in Gradle that would be
dependencies {
testImplementation("io.cloudflight.cleancode.archunit:archunit-cleancode-verifier:<version>")
}
Use the latest version as seen in the badge on top of this page instad of <version>
.
Then create a unit-test like this (the notation is Kotlin, but it also works with plain Java):
import com.tngtech.archunit.junit.AnalyzeClasses
import com.tngtech.archunit.junit.ArchTest
import com.tngtech.archunit.junit.ArchTests
import io.cloudflight.cleancode.archunit.CleanCodeRuleSets
@AnalyzeClasses(packagesOf = ["io.cloudflight.sample"])
class ArchitectureTest {
@ArchTest
val cleancode = ArchTests.`in`(CleanCodeRuleSets::class.java)
}
Change the package in the AnalyzeClasses
annotation accordingly.
That's it. Run the test now. CleanCodeRuleSets
will automatically pick up
all tests that might be for relevance for you, i.e. JPA-related tests
will only be executed if you have JPA on your classpath.
Behind the scenes, we are using an own implementation of
ArchUnit's ViolationStore
in order to achieve a slightly different behaviour
of Freezing Arch Rules.
When you run the test the first time, you will find a file
archunit_store/knownCleanCodeRules.txt
in your module.
It contains all rules that have been evaluated. Add this file to your VCS.
In case your code violates any of the rules, you will find additional files with the name of the rule inside that folder. Add those to your VCS as well.
- Your initial violations will be frozen not be reported. If you fix your violations, they will be automatically removed from those files.
- If the file is empty, the file is deleted
- If you only have the
knownCleanCodeRules.txt
in yourarchunit_store
folder, you're free from violations. Congrats! - If you wanna fix all violations immediately, remove all files but the
knownCleanCodeRules.txt
. ArchUnit will then raise errors for each of them. - If you want to refreeze all violations, add
freeze.refreeze=true
to yourarchunit.properties
as explained in the documentation
You can also ignore rules entirely with standard functionality of ArchUnit:
Create a file called archunit_ignore_patterns.txt
in src/test/resources
and add regular expressions
containing the names of the rules that you want to ignore (not only for existing code but also for all future code).
If finally violations are being reported, then you will see errors like that:
Suppose you have an entity like that:
@Entity
internal class Project(
val name: String,
) : AbstractEntity() { // contains @Id
}
This violates the rule Kotlin nullability should match JPA nullability and in the log you will see something like this:
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes that are kotlin classes and are JPA Entity classes should have only members where the nullable flag of kotlin matches the JPA column specification, because https://github.com/cloudflightio/archunit-cleancode-verifier/blob/v0.0.3/rules/jpa.md#user-content-nullable-flag-of-kotlin-needs-to-match-jpa-specification' was violated (1 times):
[io.cloudflight.tracker.domain.entity.Project]: name is nullable in the DB, but the kotlin field is not nullable
at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:110)
at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:98)
Click on the link there and get to a detailled description of the violation including code samples. We believe it's important for developers to understand the background of such issues and that usually can't be placed into a single short String.
This library requires at least JDK 17 and it is built on top of Spring Boot 3 and Jakarta EE 9