Enforcing Code Structure with ArchUnit

Stephan Prantl
willhaben Tech Blog
5 min readDec 3, 2021

--

Maintaining long-term code consistency is a well-known challenge in software development. In this article, we explore how to enforce proper code structure and easily automate it using ArchUnit.

Photo by Chris Yang on Unsplash

You approach your code. Your plan: to implement a cool feature.

But then, as soon as you begin, before you know it, the ground turns slippery, you lose balance, and you fall into mud: The code structure is muddled beyond recognition and it’s nearly impossible to even find things.

And worse, even when you find them and try to change them for the better, you are struck by its cascading effects on other unrelated parts.

Sounds familiar?

For such a situation to happen over time, multiple factors are responsible:

  • Lack of proper communication or alignment among team members
  • Insufficient onboarding of new team members
  • Team members are joining and leaving projects

… and many others. Those reasons are, in my opinion, the most important but you can also factor in missing time, budget, and so on.

As a developer, such a situation is, to put it mildly, frustrating. As well it harms the development velocity and threatens to undermine the value you offer to your users/customers.

But let’s be honest: this pain is assured in almost every project, and the projects at willhaben are no exception.

One of the projects I’m working on in the jobs team at willhaben is a greenfield project.
Of course, it’s way easier than working on historic grown legacy code. However, since every greenfield project starts from scratch, technical debt is also extremely likely.

But what if we can avoid technical debt? What if doing so doesn’t even cost you anything? What if it’s automated and you don’t have to think about it too much?

Sounds too good to be true?

No!

It is possible, and that’s exactly the goal we want to achieve!

So, go ahead: delete your twenty-page-long checklist for pull requests, and let me show you how we ensure proper code structure in an automated and thus cheap and care-free fashion.

ArchUnit to the rescue!

An extension to JUnit similar to mockito, wiremock, and other known testing libraries. ArchUnit is an excellent tool that will help you write ordinary tests focused on your architecture and tackle problems of muddled code structures and technical debt.

ArchUnit has three parts:

  1. Core API — The general implementation and basic elements of ArchUnit.
  2. Lang API — Provides a fluent DSL api / builder classes. That’s used mostly to create your own rules.
  3. Library API — Built on top of the Lang API and offers aggregated rules to define common architecture patterns to be used OOTB.

To create tests the main element is the @ArchTestannotation. This marks your ordinary test as an ArchUnit test and substitutes the @Testannotation.

Each test needs to define an ArchRuleobject which can be built by provided builder classes. The rule object is then used for evaluation on your code.

Testing naming conventions

That’s how an ArchUnit rule looks like in practice (written in kotlin using the field-style). Starting mostly with classes() as an entrypoint into the fluent DSL. This rule defines that every class annotated with @Configuration needs to have Config at the end of the simple classname.

Did you not have the same in mind when you read the code? The provided DSL is quite expressive and therefore also easily readable.

This test enhances orientation in the code. For example, when searching for a configuration class, since I know that it always ends with Config as a suffix, I can search for *Config in the IDE to easily get to it.

The first test, though it used the field-style, can also be written as a function:

Testing simple structure

Another important topic for good orientation is proper package access. It’s extremely beneficial to have clear package access in one direction, one that will be kept over time. It’s quite easy to mistakenly access another package.

When you have to deal with circular dependencies between packages, it is significantly harder to break packages down while refactoring. Moreover, circular dependencies also generate butterfly effects if you use some packages that aren’t meant for certain contexts.

Imagine you have 2 modules. The target is to streamline the access into one direction like module1 → module2 and not the other way around.

Now have a look at this example:

This rule specifies an access restriction so that Module 2 can only be accessed by Module 1 and no other modules.

This test takes care of that and even more as it also prevents access by any other module. When it’s necessary and valid that any other module accesses module 2, just extend the rule and allow it.

The main benefit of these tests is about getting automated feedback from your system when changing code.

Advanced structure testing

The ArchUnit Library API allows you to validate and enforce more complex design rules such as complete architecture patterns.

We decided in the jobs team to use the Onion Architecture / Clean Architecture / Hexagonal Architecture — how you want to name it is your choice. 😀

For detailled informations about the clean architecture pattern: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

The pattern is basically a layered architecture with a few additions.

Some of the rules for this architecture are:

  • Adapters don’t access/depend on each other
  • Input-adapters are only allowed to access the domain core
  • The domain core accesses the output-adapters but not the input-adapters
  • In general that only one layer accesses the next and no layers are skipped

All the rules coming with this architecture are covered within this quite simple test.
The only thing you need to do is to specify the name of the package and its purpose to ArchUnit.

Summary

I have illustrated only a few small examples in this article — way, way more thing are possible with ArchUnit!

Also a nice small feature is the class GeneralCodingRules which offers you generic rules for best-practices (like not writing directly to System.out/err, using constructor injection instead field injection, etc.).

Of course, how effective the rules are depends on you. Take care of what is allowed — especially what is not possible.

Treat your rules as well as you’d a dog, and you’ll get a good guard 😉

Pro

  • ArchUnit is very easy to use with the fluent DSL in the known manner as JUnit tests.
  • The fluent DSL is OOTB very powerful and also extensible with custom rules.
  • Even possible for legacy projects due to an included Violationstore.
  • Originates from java but due to how it works it’s fully compatible with kotlin.

Contra

  • Limited to the reflection api. It deals only with classes and other class-depending elements but not with files.
  • In general limited to the (test-)runtime.
  • e.g. no access to annotations with retention policy SOURCE and no access to definitions of generics. So basically every language feature erased during compilation can’t be used.

Some advanced topics I omitted but also worth to have a look on it:

  • Writing custom rules if the current possibilities are insufficient
  • Frozen Arch Rules / Violations Store — Enables you to use ArchUnit even within legacy code structures
  • Calculation of architecture metrics provided (e.g. Cumulative Dependency Metrics by John Lakos)

Though ArchUnit’s inception was in the java ecosystem, there is now also a port to .NET/C#.

Links:

ArchUnit Java — https://www.archunit.org/

.NET/C# — https://archunitnet.readthedocs.io/en/latest/

Maven plugin — https://github.com/societe-generale/arch-unit-maven-plugin

Gradle plugin — https://github.com/societe-generale/arch-unit-gradle-plugin

--

--