In our architecture guidelines, we should only instantiate business exceptions from within the domain model or repository implementations (which are in the infrastructure layer)
We usually create the exceptions with factory methods.
So I would like to have a ArchUnit rule for that. Which should go along the lines:
Only classes residing in domain
or are implementations of Repositories
are allowed to call static methods of classes annotaed with @BusinessException
All of our business exceptions are annotated with @BusinessException. So it is easy to find them.
I tried variations of that:
noClasses().that().resideOutsideOfPackages(DOMAIN).or(areImplementing(Repository.class))
.should().callMethodWhere(
target(owner(isAnnotatedWith(BusinessException.class)))
.and(target(modifier(JavaModifier.STATIC))));
areImplementing()
is a custom predicate to find out if a class is the implementation of a repository.
This code does not compile. isAnnotatedWith
cannot be used like that.
I also tried variations of
methods().that().areStatic()
.and().areDeclaredInClassesThat().areAnnotatedWith(BusinessException.class)
.should().onlyBeCalled().byClassesThat(areImplementing(Repository.class))
again, this does not compile, onlyBeCalled
does not exist.
Has someone an idea?
I'm not entirely sure if there's a shorter form to express what you want, but I believe the following snippet which uses a custom predicate does the job.
noClasses().that()
// Restriction applies to all classes outside DOMAIN package that are not Repository classes
.resideOutsideOfPackages(DOMAIN)
.and()
.areNotAssignableFrom(Repository.class)
.should()
.callMethodWhere(describe("static methods in BusinessExceptions",
// Target method may not reside in class annotated with BusinessException
methodCall -> methodCall.getTarget().getOwner().isAnnotatedWith(BusinessException.class)
// And target method may not have the static modifier
&& methodCall.getTarget()
.resolve()
.stream()
.anyMatch(m -> m.getModifiers().contains(JavaModifier.STATIC))));
The only bit I'm uncertain about is that methodCall.getTarget().resolve()
returns a set of JavaMethod classes.