The Benefits of Codifying and Asserting Architectural Quality

Daniel Bryant
97 Things
Published in
3 min readJun 28, 2019

--

Your continuous delivery build pipeline should be the primary location where agreed-upon architectural qualities for your applications are codified and enforced. However, these automated quality assertions shouldn’t replace continued team discussions about standards and quality levels, and they should definitely not be used to avoid intra- or inter-team communication. That said, checking and publishing quality metrics within the build pipeline can prevent the gradual decay of architectural quality that might otherwise be hard to notice.

If you’re wondering why you should test your architecture, the ArchUnit motivation page (archunit.org/motivation) has you covered. It starts with a familiar story: Once upon a time, an architect drew a series of nice architectural diagrams that illustrated the components of the system and how they should interact. Then the project got bigger and use cases more complex, new developers dropped in and old developers dropped out. This eventually led to new features being added in any way that fit. Before long, everything depended on everything, and any change could have an unforeseeable effect on any other component. I’m sure many readers will recognize this scenario.

ArchUnit is an open source, extensible library for checking the architecture of your Java code by using a Java unit-test framework like JUnit or TestNG. ArchUnit can check for cyclic dependencies, check dependencies between packages and classes, and layers and slices, and more. It does all this by analyzing Java bytecode and importing all classes for analysis.

To use ArchUnit in combination with JUnit 4, include the following dependency from Maven Central:

<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit</artifactId>
<version>0.5.0</version>
<scope>test</scope>
</dependency>

At its core, ArchUnit provides infrastructure to import Java bytecode into Java code structures. You can do this using ClassFileImporter. You can make architectural rules such as “services should be accessed only by controllers” by using a DSL-like fluent API, which can in turn be evaluated against imported classes:

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
// ...
@Test
public void Services_should_only_be_accessed_by_Controllers() {
JavaClasses classes =
new ClassFileImporter().importPackages("com.mycompany.myapp");
ArchRule myRule = classes()
.that().resideInAPackage("..service..")
.should().onlyBeAccessed()
.byAnyPackage("..controller..", "..service..");
myRule.check(classes);
}

Extending the preceding example, you can also enforce more layer-based access rules using this test:

@ArchTest
public static final ArchRule layer_dependencies_are_respected = layeredArchitecture()
.layer("Controllers").definedBy("com.tngtech.archunit.example.controller..")
.layer("Services").definedBy("com.tngtech.archunit.example.service..")
.layer("Persistence").definedBy("com.tngtech.archunit.example.persistence..")
.whereLayer("Controllers").mayNotBeAccessedByAnyLayer()
.whereLayer("Services").mayOnlyBeAccessedByLayers("Controllers")
.whereLayer("Persistence").mayOnlyBeAccessedByLayers("Services");

You can also ensure that naming conventions such as class name prefixes are followed, or specify that a class named a certain way must be in an appropriate package. GitHub contains a host of ArchUnit examples (github.com/TNG/ArchUnit-Examples) to get you started and give you ideas.

You could attempt to detect and fix all of the architectural issues mentioned here by having an experienced developer or architect look at the code once a week, identify violations, and correct them. However, humans are notorious for not acting consistently and, when the inevitable time pressures are placed on a project, often the first thing to be sacrificed is manual verification.

A more practical method is to codify the agreed-upon architectural guidelines and rules using automated tests, using ArchUnit or another tool, and include them as part of your continuous integration build. Any issues can then be quickly detected and fixed by the engineer who caused the issue.

--

--

Daniel Bryant
97 Things

DevRel and Technical GTM Leader | News/Podcasts @InfoQ | Web 1.0/2.0 coder, platform engineer, Java Champion, CS PhD | cloud, K8s, APIs, IPAs | learner/teacher