I'm working on a Java project where we have a specific requirement: all BigDecimal instances should be created exclusively through a utility class, let's call it MathUtils. The goal is to ensure consistent application of a MathContext and other configurations.
To enforce this rule across our codebase, I am looking into using ArchUnit. However, I'm facing challenges in formulating a rule that ensures BigDecimal is only instantiated within MathUtils and not directly in other parts of the application.
Here's what I've tried so far:
@ArchTest
public static final ArchRule bigDecimalShouldOnlyBeInstantiatedInMathUtils = noClasses()
.that().doNotHaveFullyQualifiedName("a.b.c.MathUtils")
.should().callConstructor(BigDecimal.class)
.because("BigDecimal should only be instantiated in MathUtils");
I am looking for guidance on how to properly set up an ArchUnit rule for this purpose, or if there's another approach or tool I should consider for enforcing such architectural constraints in Java.
Key Points:
Any insights or suggestions would be greatly appreciated!
In my opinion, it is a good idea to use ArchUnit to check wanted / unwanted dependencies between Java classes. Another alternative would be jQAssistant.
For your specific requirement, 2 more things need to be considered:
callConstructor(BigDecimal.class)
only checks for calls to the no-args constructor, because the method is actually defined as callConstructor(Class<?> owner, Class<?>... parameterTypes)
valueOf
methods, where I assume that these calls are also not wanted.Considering these 2 aspects, you could write this rule:
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target;
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.targetOwner;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.has;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.is;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
@ArchTest
public static final ArchRule bigDecimalShouldOnlyBeInstantiatedInMathUtils = noClasses()
.that().doNotHaveFullyQualifiedName("a.b.c.MathUtils")
.should().callConstructorWhere(targetOwner(is(equivalentTo(BigDecimal.class))))
.orShould().callMethodWhere(targetOwner(is(equivalentTo(BigDecimal.class))).and(target(has(name("valueOf")))))
.as("BigDecimal should only be instantiated in MathUtils");
Note that I changed .because(..)
to .as(..)
to create a shorter rule description for error messages, because the original one may be too verbose.