Reputation: 1083
I have two packages, one for interfaces and one for implementations. I would like to restrict access of the classes to their implemented interfaces only. Say:
package com.interfaces
interface AI {
}
package com.interfaces
interface BI {
}
package com.impls
class A implements AI{
}
package com.impls
class B implements BI{
}
I already have a rule to make sure each class in com.impls implements an interface from com.interfaces:
ArchRuleDefinition.classes()
.that().resideInAPackage("com.impls..")
.should().implement(JavaClass.Predicates.resideInAPackage("com.interfaces.."))
Now I need to make sure a class only accesses the interface it implements. I could also rely on the naming convension as in the example above, if this helps. I am looking for something like:
ArchRuleDefinition.classes()
.that().resideInAPackage("com.impls..")
.should().implement(JavaClass.Predicates.resideInAPackage("com.interfaces.."))
.andShould().onlyHaveDependentClassesThat().haveSimpleNameContaining("THE NAME OF THE IMPLEMENTING CLASS")
We will be using the interfaces everywhere and the implementations will be injected (Spring Boot). But an implementation is not allowed to access interfaces it doesn't implement. So AI is allowed to access A but not B. BI is allowed to access B but not A! The interfaces, on the other hand, are not restricted to the implementations. They will be used in other packages.
Is there a way to reuse names from the first part of the rule or to restrict the access in another way in ArchUnit?
Upvotes: 0
Views: 2569
Reputation: 3062
If you want to prevent that any class other than A
itself depends on A
etc., you can use
ArchRuleDefinition.classes()
.that().resideInAPackage("com.impls..")
.should().implement(JavaClass.Predicates.resideInAPackage("com.interfaces.."))
.andShould(new ArchCondition<JavaClass>("not have other classes depending on them") {
@Override
public void check(JavaClass javaClass, ConditionEvents events) {
List<JavaClass> otherClassesThatDependOnJavaClass = javaClass.getDirectDependenciesToSelf().stream()
.map(Dependency::getOriginClass)
.filter(origin -> !origin.equals(javaClass)) // TODO: inner classes?
.collect(toList());
boolean satisfied = otherClassesThatDependOnJavaClass.isEmpty();
String message = satisfied
? "no other classes depend on " + javaClass.getName()
: otherClassesThatDependOnJavaClass.stream().map(JavaClass::getName)
.collect(joining(", ", "other classes depend on " + javaClass.getName() + ": ", ""));
events.add(new SimpleConditionEvent(javaClass, satisfied, message));
}
});
where you might want to adapt the filter (see TODO
) to account for inner classes if that's a use case for you.
The .filter(origin -> !origin.getSimpleName().contains(javaClass.getSimpleName()))
you asked for is easily possible, but probably too permissive.
Upvotes: 1