archunit

ArchUnit to stop changing the state of instance variables within Spring beans


I have Spring beans this way

@Service
public class HotelsService {

    public List<Hotel> fetchAllHotels() {
        return dummyHotelsList();
    }
}

I should stop my developers from creating an instance variable within this bean and start changing the state within a method (see below snippet which i dont want to happen). Need to do this via archUnit. Didn't want to catch this during code review process

@Service
public class HotelsService {
    private int someInteger;

    public List<Hotel> fetchAllHotels() {
        this.someInteger=1; // just for example
        return dummyHotelsList();
    }
}

I am ok to have instance variables and just read it. But don't want to write on it.

Any straightforward implementation in ArchUnit ?


Solution

  • You may have to implement a custom (but straightforward... 😁) ArchCondition:

    ArchRule service_methods_should_not_set_service_fields =
        methods()
            .that().areDeclaredInClassesThat().areAnnotatedWith(Service.class)
            .should(new ArchCondition<JavaMethod>("not set field of @Service class") {
                @Override
                public void check(JavaMethod method, ConditionEvents events) {
                    method.getFieldAccesses().stream()
                        .filter(accessType(SET))
                        .filter(target(With.owner(annotatedWith(Service.class))))
                        .forEach(access -> events.add(violated(access, access.getDescription())));
                }
            })
            .because("beans should not have state");
    

    using

    import com.tngtech.archunit.core.domain.JavaMethod;
    import com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With;
    import com.tngtech.archunit.lang.ArchCondition;
    import com.tngtech.archunit.lang.ArchRule;
    import com.tngtech.archunit.lang.ConditionEvents;
    
    import static com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType.SET;
    import static com.tngtech.archunit.core.domain.JavaFieldAccess.Predicates.accessType;
    import static com.tngtech.archunit.core.domain.JavaFieldAccess.Predicates.target;
    import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith;
    import static com.tngtech.archunit.lang.SimpleConditionEvent.violated;
    import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
    

    Your example fails with:

    Rule 'methods that are declared in classes that are annotated with @Service should not set field of @Service class, because beans should not have state' was violated (1 times):
    Method <HotelsService.fetchAllHotels()> sets field <HotelsService.someInteger> in (source code location)