Home

ArchUnit and Shifting the Burden

In The Fifth Discipline Peter Senge introduces the concept of “shifting the burden”. In the book it is covered as a poor approach to dealing with issues found in complex systems; people shift the burden by in effect applying a sticking plaster:

An underlying problem generates symptoms that demand attention. But the underlying problem is difficult for people to address, either because it is obscure or costly to confront. So people “shift the burden” of their problem to other solutions—-well-intentioned, easy fixes which seem extremely efficient. Unfortunately, the easier “solutions” only ameliorate the symptoms; they leave the underlying problem unaltered. The underlying problem grows worse, unnoticed because the symptoms apparently clear up, and the system loses whatever abilities it had to solve the underlying problem.

But shifting responsibilities is an unavoidable part of working with complex systems. For Senge it is a problem only insofar people use the tactic to obfuscate underlying issues. If the responsibility is simply unavoidable, the burden must be borne by something. The question is what? There are probably some guidelines to advise here:

  1. If a responsibility is unavoidable, the execution of that responsibility should be made explicit and observable.
  2. If a responsibility is unavoidable, and must be repeated, it should not be done by a human.

In short, such burdens should be automated and they should be made visible. Doing so frees people who can then spend more of their time solving problems that are too complex to be automated, and observability ensures the burden is not forgotten about. Though a machine may do it, we must not forget that it occurs, so that if the burden worsens we notice this and respond appropriately.

The Burden of Structural Integrity

If you have worked on a project with a defined, but not codified, architecture, you will know it is a considerable burden to protect the design from rot. Repeatedly, you are required to check that changes applied (including your own) comply with the design as it is. The burden remains even with traditional documentation as while one can read and understand the document, they may not then apply the guidance.

Thankfully, there are now several libraries to assert architectural integrity as part of the testing flow of a system. Based on the test, these libraries scan the code to check for relationships and interactions that do not conform to the rules defined. In the .NET space, the tool I chose for this is ArchUnitNET, which is a port of the Java ArchUnit library. An example test, taken from the GitHub README, demonstrates how one would protect a layered architecture:

[Fact]
public void ExampleClassesShouldNotCallForbiddenMethods()
{
    var forbiddenMembers = MethodMembers()
	.That()
	.AreDeclaredIn(ForbiddenLayer)
	.Or()
	.HaveNameContaining("forbidden");

    Classes()
	.That()
	.Are(ExampleClasses)
	.Should()
	.NotCallAny(forbiddenMembers)
	.Check(Architecture);
}

By defining the architecture as code, these libraries can bear the burden of guiding compliance. Written well, each test can be made self-explanatory, with the option to give a reason for more nonobvious tests. Here’s one that prevents usage of a third-party ORM’s interface directly, explaining that an in-house wrapper must be used instead:

[Fact]
public void ProtectAgainstContextUsage()
{
    var contexts = Types()
	.That()
	.ImplementInterface(typeof(IDbContext));

    Classes()
	.That()
	.AreDeclaredIn(ServiceLayer)
	.Should()
	.NotDependOnAny(contexts)
	.Because("the in-house interface that wraps the ORM should be used instead.")
	.Check(Architecture);
}

Such tests integrate with runners like XUnit and NUnit, meaning checks can be made for every CI build on shared branches, or as part of pull request completion policies. If the architecture tests fail, the developer must fix their code to comply with the design as per the tests, before integrating their changes. This lifts a huge burden off longer serving team members/software architects, meaning they can Over the past few years I have spent considerable effort maintaining the architectural integrity of the main system I work on. This has been necessary to ensure new members joining the team follow the agreed patterns in use, that they honour the separate responsibilities of the various components. I have found this requires regular effort, likely because I did not make clear the why behind such restrictions; I did not explain the overarching philosophy in play. But even if I had, I would still be required to ensure the agreed approach is followed whenever a new member joins the team, or a returning member needs a refresher on the patterns in play. This is a burden, but until recently I did not think of it as such, and I certainly did not think of it as automatable. spend more time at code review discussing what really matters: how well the business case is being met by the implementation proposed.

In addition to ensuring compliance, they are also educational tools for new team members, who can now explore the architecture safely by making mistakes. Every failing architecture test becomes a lesson in not only what the rules are, but why they are so. The parameter to the Because call in previous example could include more information detailing that the ORM is wrapped to reduce dependency on third-party libraries in the business domain. In this way, team members learn the paradigms guiding the rules, which is a much more powerful lesson to teach.