Search code examples
javaarchunit

Restricting access to static methds with ArchUnit


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?


Solution

  • 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.