ArchUnit
ArchUnit copied to clipboard
Add more examples to the documentation
There are only a few examples for common use cases but I have the feeling that ArchUnit is much more powerful. But for me, it's impossible to find solutions for use cases like this: https://stackoverflow.com/questions/71077218/archunit-check-method-calls
So it would be very useful to have examples like "how to check if a method in a class calls a method in another class"
Also if I look at the solution I'm not really sure how this works and what the steps are doing and how to use the API:
noClasses()
.that(not(name(Bar1.class.getName()))
.and(not(name(Bar2.class.getName()))))
.should().callMethodWhere(target(nameMatching("method1"))
.and(target(owner(assignableTo(Foo.class)))))
.orShould().callMethodWhere(target(nameMatching("method2"))
.and(target(owner(assignableTo(Foo.class)))));
Something like a guide what the concepts in the API are and how to use it would be very helpful.
Thanks for raising this issue! I'd like the docs to be as helpful as possible. My original hope was that the concept described here plus the ArchUnit Examples would provide enough guide lines to quickly figure out most use cases. But, I'm all open to improve the docs if that is not the case.
The question for me is how to do this structurally. Of course I could add a section and describe how to solve the case you're mentioning, but this is only one case and there seem to be many more out there that maybe would deserve the same attention then :thinking:
How would you imagine such docs to help you solve your case? Should they be hierarchical in some way? And how would we decide which use cases need to go into the docs and which are too specific?
When I look at my example I see that most of the API methods are not described in the Guide.
On the other hand, the API has no comments: https://javadoc.io/doc/com.tngtech.archunit/archunit/latest/com/tngtech/archunit/lang/conditions/ArchConditions.html#callMethodWhere(com.tngtech.archunit.base.DescribedPredicate)
Maybe the issue is also because the API leads to a lot of nested method calls and some of the methods are ambiguous so it's hard for the IDE and the programmer to find out which method to import.
I've read the docs but can't say what really is missing. Maybe my use case where I want to check if a class is calling a certain method in another class is not so nicely supported as checking for class dependencies or method properties.
A short and unsorted collection of my thoughts…
The example
noClasses()
.that(not(name(Bar1.class.getName()))
.and(not(name(Bar2.class.getName()))))
gives me a headache because of the double negation. Maybe we should introduce a onlyClasses() to have
onlyClasses()
.that(name(Bar1.class.getName())
.or(name(Bar2.class.getName())))
that fails, if classes that don't match the that condition, match the should condition?
For me it is always difficult to find the static methods which I have to import. For example, what I like about jOOQ is that there is a single class DSL as entry point and where all static methods are collected. Same goes for AssertJ and the Assertions class.
I admit that I maybe remember that classes() is located in ArchRuleDefinition, but not that name(..) is in HasName.Predicates. Maybe we could also introduce a class ArchUnit as entry point for everything?
What would also help me would be hints on where to look next. For example, if I don't find anything suitable in should(), I would expect in the Javadoc of should() or should(..) where I could search further to build my own condition with should(..).
Something other that I like about AssertJ are the examples for passing and failing asserts within the Javadoc, e.g. for containsExactly. For me, such examples are helpful to find the right method.
As to ArchUnit-Examples: I never really looked into those examples. Therefore, I also do not know what is available there. Maybe it would be helpful if we generate all code examples and a description into the documentation? Then we would have everything searchable on one page.
Very good points @rweisleder :smiley: The only thing about the central entry point for all predicates / conditions is that I wonder if it would clash somehow :thinking: But in the end we could also mimic the hierarchies there :thinking: I.e. could do Predicates > dot > autocomplete[ForClasses/ForMethods/ForFields/...] > ForMethods > dot > autocomplete[name/modifier/...] or similar :man_shrugging:
But I agree, at the moment you need to know the domain model pretty well to know where to look for those predicates. And it's not consistent because the conditions actually are collected in a single place :see_no_evil:
And also true about the Javadoc, it's missing for some methods that should have it (like the mentioned callMethodsWhere(..))
On the other hand, HasName.Predicates has the advantage that you can apply those to all things with names :thinking: If we have Predicates.ForClasses...., etc., we have to duplicate that entrypoint for every domain object. But maybe that's also okay to make it easier :man_shrugging:
For me it is always difficult to find the static methods which I have to import. For example, what I like about jOOQ is that there is a single class DSL as entry point and where all static methods are collected. Same goes for AssertJ and the Assertions class. I admit that I maybe remember that
classes()is located inArchRuleDefinition, but not thatname(..)is inHasName.Predicates. Maybe we could also introduce a classArchUnitas entry point for everything?
I hacked a small example for this one using annotation processing and JavaPoet, see https://github.com/rweisleder/ArchUnit/commit/75bc4a2ff42c0ebb4c8f562bcf34bac62858bf14. (It was on my to-do list to experiment with both of them anyway. 🤪)
The result is a single entry class ArchUnit which looks like this: https://gist.github.com/rweisleder/f0dd45fd5449a6e57901fb445753460c
The only thing about the central entry point for all predicates / conditions is that I wonder if it would clash somehow 🤔
Unfortunately, there are clashes. This class has these compile errors:
...\ArchUnit\archunit\build\generated\sources\annotationProcessor\java\main\com\tngtech\archunit\ArchUnit.java:285: error: method constructor() is already defined in class Predicates
public static DescribedPredicate<JavaCodeUnit> constructor() {
^
...\ArchUnit\archunit\build\generated\sources\annotationProcessor\java\main\com\tngtech\archunit\ArchUnit.java:390: error: method declaredIn(DescribedPredicate<? super JavaClass>) is already defined in class Predicates
public static DescribedPredicate<JavaMember> declaredIn(
^
...\ArchUnit\archunit\build\generated\sources\annotationProcessor\java\main\com\tngtech\archunit\ArchUnit.java:405: error: method declaredIn(Class<?>) is already defined in class Predicates
public static DescribedPredicate<JavaMember> declaredIn(Class<?> clazz) {
^
...\ArchUnit\archunit\build\generated\sources\annotationProcessor\java\main\com\tngtech\archunit\ArchUnit.java:419: error: method declaredIn(String) is already defined in class Predicates
public static DescribedPredicate<JavaMember> declaredIn(String className) {
^
...\ArchUnit\archunit\build\generated\sources\annotationProcessor\java\main\com\tngtech\archunit\ArchUnit.java:825: error: name clash: target(DescribedPredicate<? super CodeUnitCallTarget>) and target(DescribedPredicate<? super CodeUnitAccessTarget>) have the same erasure
public static DescribedPredicate<JavaCall<?>> target(
^
...\ArchUnit\archunit\build\generated\sources\annotationProcessor\java\main\com\tngtech\archunit\ArchUnit.java:833: error: name clash: target(DescribedPredicate<? super CodeUnitReferenceTarget>) and target(DescribedPredicate<? super CodeUnitAccessTarget>) have the same erasure
public static DescribedPredicate<JavaCodeUnitReference<?>> target(
^
...\ArchUnit\archunit\build\generated\sources\annotationProcessor\java\main\com\tngtech\archunit\ArchUnit.java:841: error: name clash: target(DescribedPredicate<? super FieldAccessTarget>) and target(DescribedPredicate<? super CodeUnitAccessTarget>) have the same erasure
public static DescribedPredicate<JavaFieldAccess> target(
^
...\ArchUnit\archunit\build\generated\sources\annotationProcessor\java\main\com\tngtech\archunit\ArchUnit.java:849: error: name clash: target(DescribedPredicate<? super AccessTarget>) and target(DescribedPredicate<? super CodeUnitAccessTarget>) have the same erasure
public static DescribedPredicate<JavaAccess<?>> target(
^
Anyway, maybe one can reuse my example for a different approach.
Not a full-fledged solution, but I added a lot more docs with links in #912. That should at least improve the discoverability a little by pointing from places that take DescribedPredicate<DomainObject> to DomainObject.Predicates and docs from there that point to CommonInterface.Predicates. E.g. the chain DescribedPredicate<JavaClass> -> JavaClass.Predicates -> HasName.Predicates can now at least be followed in Javadoc...