TNG / ArchUnit

A Java architecture test library, to specify and assert architecture rules in plain Java
http://archunit.org
Apache License 2.0
3.18k stars 287 forks source link

[How to] Do whitelist access to same class (private) methods #871

Closed sebarthel89 closed 2 years ago

sebarthel89 commented 2 years ago

We use a layered architecture where one layer is called "application". You might know that from a domain driven design package structure, which comes with layers for interfaces, application, domain and infrastructure.

We have the following code to do not allow access from classes with suffix ApplicationService to other classes called ApplicationService.

            ArchRuleDefinition.noClasses()
                .that()
                .resideInAnyPackage("${assertions.basePackageName}..")
                .and()
                .haveSimpleNameEndingWith(settings.applicationServiceSuffix)
                .should()
                .accessClassesThat()
                .haveSimpleNameEndingWith(settings.applicationServiceSuffix)

This worked nicely with version 0.11.0. Now, we updated to version 0.23.0 which causes problems. The presented rule shows violation method calls of the same class. The following class violates the rule:

internal class SelfAccessPrivateMethodApplicationService() {

    fun method() {
        privateMethod()
    }

    private fun privateMethod() {}

}

To be concrete, it violates because the method method calls a method privateMethod of a class with the suffix ApplicationService, that is the same class. We would like to exclude this rule violating for any access within the same class that worked like that in previous versions.

Maybe I do miss something here that is now available in newer version. I would appreciate suggestions or maybe it's a bug?

Thanks in advance !

hankem commented 2 years ago

This was a conscious breaking change with ArchUnit 0.12.0 for #209.

You can use a custom ArchCondition that excludes accesses to self:

// ...
.should(new ArchCondition<JavaClass>("access other classes that have simple name ending with Service") {
    @Override
    public void check(JavaClass javaClass, ConditionEvents events) {
        javaClass.getAccessesFromSelf().stream().filter(access ->
                !access.getTargetOwner().equals(javaClass) && access.getTargetOwner().getName().endsWith("Service")
            )
            .forEach(access -> events.add(SimpleConditionEvent.satisfied(javaClass, access.getDescription())));
    }
});
sebarthel89 commented 2 years ago

thank you !