Optimizing Software Architecture: Fitness Functions Using ArchUnit

Share

Software architecture is challenging and ensuring adherence to architectural guidelines is even more difficult. In this article, we will explore how to solve this problem using the concept of fitness functions with ArchUnit in Java. 

What are Architecture Fitness Functions?

One of the challenges architects often face is ensuring that the team follows the architecture guidelines while translating the architecture to code. To some extent, we can keep architecture in check for small teams using practices like code reviews and pair programming. However, ensuring everyone adheres to the architectural guidelines becomes difficult when the team is large or geographically distributed.

Architecture fitness functions are an automated and effective way to validate the architecture guidelines.  Simply put, fitness functions are unit tests that check for different architecture rules in a codebase. Furthermore, the most effective way to use fitness functions is to integrate these to run in the CI/CD pipelines and validate the architecture.

Introduction to ArchUnit

ArchUnit is a free, simple, and extensible library for checking the architecture of Java code using any plain Java unit test framework. It provides APIs for checking various architecture rules. 

For example, we can write unit tests using ArchUnit to check:

  • dependencies between classes and packages
  • the naming convention of the classes
  • correct usage of annotations in code
  • code flow in a layered architecture

We’ll see the example of some of these use cases in the following sections.

Writing Our First Fitness Function with ArchUnit

First, we will create a simple Spring boot application using Spring Initializr

Adding ArchUnit Dependency

Once we import the generated application in an IDE, let’s add the ArchUnit maven dependency in the pom.xml. We’ll be using junit5 here.

<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.3.0</version>
<scope>test</scope>
</dependency>
Setting up the Use Case

Now, let’s create a controller package and create two classes in this package named, CustomerController and CustomerAPI

Let’s consider a scenario where two developers have created these classes without being aware of the naming convention we want to follow for controllers. Ideally, we would have preferred to have all classes in the controller packages named with suffix ‘Controller’.

fitness functions

Therefore, to ensure the naming convention is followed, let’s write our first fitness function using ArchUnit.

Writing the Fitness Function

Since the fitness functions are essentially unit tests, we will create a class under src/main/test named ClassNamingTests, which will contain our first fitness function.

@AnalyzeClasses(packages = "com.technicalmusings.spring")
public class ClassNamingTests {

@ArchTest
private final ArchRule classes_in_controller_packages_should_have_controller_suffix =
classes().that().resideInAPackage("..controller..").
should().haveSimpleNameEndingWith("Controller");

}

Here, the @AnalyzeClasses annotation imports the classes we want to check the rules. The @ArchTest annotation, on the other hand, is a wrapper over Junit’s @Testable annotation indicating that the annotated ArchRule or method is testable.

Clearly, it is easy to write and understand the fitness functions using ArchUnit. This is due to the fluent API design, ArchUnit uses.

Testing the Fitness Function

Now, without further ado, let’s execute the fitness function as a test case. We will observe how test case fails and serves as a reminder to follow the rule. On my setup, it fails with the following message:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes that reside in a package '..controller..' should have simple name ending with 'Controller'' was violated (1 times):
Class <com.technicalmusings.spring.controller.CustomerAPI> does not have simple name ending with 'Controller' in (CustomerAPI.java:0)ssddsd

As you can see, the fitness function failure message is quite verbose. 

ArchUnit Examples

Let’s now explore a few more examples of fitness functions using ArchUnit. 

Example 1: Verify the control flow in a layered architecture.

@ArchTest
private final ArchRule layered_architecture_should_have_top_to_bottom_control_flow =
layeredArchitecture()
.consideringAllDependencies()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Repository").definedBy("..repository..")
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
.whereLayer("Repository").mayOnlyBeAccessedByLayers("Service");

Example 2: Verify the correct usage of annotations

@ArchTest
private final ArchRule all_service_classes_should_be_annotated_with_service =
classes().that().resideInAPackage("..service..").should().beAnnotatedWith("Service");

Example 3: Verify that no classes are using field injection

@ArchTest
private final ArchRule no_classes_should_use_field_injection = NO_CLASSES_SHOULD_USE_FIELD_INJECTION;

In this example, we are using a predefined generic coding rules from the package com.tngtech.archunit.library.GeneralCodingRulesLet’s see a couple of more examples of these predefined generic coding rules.

Example 4: Verify that we are not using System.out.println.

@ArchTest
private final ArchRule no_classes_should_use_system_out = NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS;

Example 5: Verify that our code does not throw any generic exception.

@ArchTest
private final ArchRule no_classes_should_throw_generic_exception = NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS;

Example 6: Verify that all logger variables are defined as private, static, and final.

@ArchTest
private final ArchRule loggers_should_be_private_static_final =
fields().that().haveRawType(Logger.class)
.should().bePrivate()
.andShould().beStatic()
.andShould().beFinal();

Notice, we have used a field level check here. This shows the power of ArchUnit, we can not only write fitness functions for classes but also fields.

You can find these fitness functions in the MiscArchUnitTests class. You can download the code and run it to see these fitness functions in action. In this code, I made sure that all fitness functions are failing. Therefore, once you execute the fitness functions, you can check the failure logs as well.

Conclusion

In conclusion, fitness functions can be handy in ensuring our architecture meets the predefined quality criteria. In this article, we saw how ArchUnit makes writing fitness functions straightforward, using some examples. 

What makes me wonder is that even though the fitness functions are so easy to implement and effective, I haven’t seen these being used that often. If you like the concept, do give a try to implement fitness functions in your project and let me know how it goes.

You can read more articles on software architecture here.

 


Share

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top