Clean Architecture with ArchUnit

Enforcing a layered architecture with tests

  1. Setup
  2. Rules
  3. Conclusion

In a previous post I discussed ways in which AOP can help in defining our architecture, even allowing us to verify architectural rules at compile time.

While this approach certainly does work, it has a few drawbacks, like its reliance on AOP, which is not always an option. Furthermore, the way these rules are defined is slightly unnatural.

A more idiomatic way of automatically verifying one’s architecture is ArchUnit, which enables us to define our rules as unit tests.

Originally a Java library, the project has since been ported to C#, which makes sense, considering Java Kotlin and C# are the same language. If a truly useful library is created in either of these, it will eventually be ported to the others, just like for AspectJ we now got PostSharp.

Anyway, this is convenient for me, since I wanted to take a look at enforcing a clean architecture in C# today. (Please read Uncle Bob’s post)

In the Java ArchUnit, there is out of the box support for both layered and onion architectures, but not in the dotnet ports.

I won’t be going for a full-blown onion / clean arch as my purpose is to slowly evolve and improve the architecture of my project. Taking things from 0 to 100 in one step is likely not gonna benefit anyone, and it is easier to adapt to small, incremental changes. So, while the end goal is a clean architecture, for now I just want to implement some simple layer checks, similar to the ones shown in the image below.

Setup

I found two candidates for ArchUnit-style tests:

I decided to go with the former, as it is from TNG, the same team as the original library.

After installing, I noticed there is no assert support for MSTest, so I recommend to just copy the related classes from the xUnit folder.

Here is what I ended up with.

Rules

The individual layers can be defined like this:

var serviceLayer     = Types().That().ResideInNamespace(".Service");
var controllerLayer  = Types().That().ResideInNamespace(".Controller");
var peristenceLayer  = Types().That().ResideInNamespace(".Repository");

Let’s start with our first rule: None of our code should depend on any controller / inbound port.

[TestMethod]
public void LayeredArch_Controller()
{
    var rule = Types().That().AreNot(this.controllerLayer)
        .Should().NotDependOnAny(this.controllerLayer);

    rule.Check(Architecture);
}

The next rule prevents access from the persistence layer to the service layer.

Types().That().Are(this.peristenceLayer)
    .Should().NotDependOnAny(this.serviceLayer);

And lastly, only allow the service layer to access the persistence layer.

Types().That().AreNot(this.serviceLayer)
    .Should().NotDependOnAny(this.peristenceLayer);
}

The complete test class is available here.

Conclusion

ArchUnit provides an elegant way of defining architectural rules, helping you enforce your design. Whilst the AOP compile-time approach is great, too, I prefer an AOP-independent solution.

It would be great to have the layer and onion dsl ported to C#, too.

I will be experimenting with the layers and slowly approach a clean architecture.


© 2023. All rights reserved.