Reputation: 5223
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!
Upvotes: 0
Views: 103
Reputation: 3142
You're almost there! Note, however, that callConstructor
(BigDecimal.class)
would only match a BigDecimal
constructor without parameters, which is probably not what you want (and which doesn't even exist in my JDK).
If you want to forbid any BigDecimal
constructor call, you could use the following rule:
@ArchTest
ArchRule bigDecimalShouldOnlyBeInstantiatedInMathUtils = noClasses()
.that().doNotHaveFullyQualifiedName(MathUtils.class.getName())
.should().callConstructorWhere(describe("target is BigDecimal", constructorCall ->
constructorCall.getTargetOwner().isEquivalentTo(BigDecimal.class)
))
.because("BigDecimal should only be instantiated in MathUtils");
Upvotes: 1
Reputation: 10531
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.
Upvotes: 1