If Software Architecture is done to a reasonable standard, we should expect to see:
Arch Unit is a super mechanism to impose Architectural rules and patterns on your code base. It has been around a few years but something I only discovered this year. I came across it when I was trying to think of ways to ensure the proverbial "utils" package did not turn into the proverbial "dumping ground". Ideally, we'd have no utils packages ever. But, in the real world they nearly always exist. The utils package shouldn't have many efferent dependencies. So for example, suppose you have a package called shoppingcart. Then you have need some sort of utility function to add the total of two carts, remove special offers, add loyalty discounts, blah blah blah. The last thing you want to see is someone checking that into the utils package with dependencies towards the shoppingcart package. Because, if it is so shoppingcart focused, it should really just be in the shoppingcart package. If this happens, very soon your utils package will have dependencies to everything and everything will have dependencies to it. Disaster. What is the point in packages if anything can just depend on anything? They will cease to provide any name-spacing or encapsulation benefits.
So, how can Arch Unit help? Well very simple you define Architectural rules like a JUnit test. Wait a sec... It is a JUnit test. The efferent (outward) and afferent (inward) for you utils package are very simple expressed as:
Not only does ArchUnit give you the ability to express package rules, you can also define your own rules aka conditions and then apply them to whatever code you want. For example, suppose you want a condition that an object is immutable. You naturally therefore want no setters. That could be expressed by this condition.
- Well designed patterns that can fulfill both functional requirements and non-functional requirements
- No crazy crazy coupling, concerns are properly separated and everything is testable.
Arch Unit is a super mechanism to impose Architectural rules and patterns on your code base. It has been around a few years but something I only discovered this year. I came across it when I was trying to think of ways to ensure the proverbial "utils" package did not turn into the proverbial "dumping ground". Ideally, we'd have no utils packages ever. But, in the real world they nearly always exist. The utils package shouldn't have many efferent dependencies. So for example, suppose you have a package called shoppingcart. Then you have need some sort of utility function to add the total of two carts, remove special offers, add loyalty discounts, blah blah blah. The last thing you want to see is someone checking that into the utils package with dependencies towards the shoppingcart package. Because, if it is so shoppingcart focused, it should really just be in the shoppingcart package. If this happens, very soon your utils package will have dependencies to everything and everything will have dependencies to it. Disaster. What is the point in packages if anything can just depend on anything? They will cease to provide any name-spacing or encapsulation benefits.
So, how can Arch Unit help? Well very simple you define Architectural rules like a JUnit test. Wait a sec... It is a JUnit test. The efferent (outward) and afferent (inward) for you utils package are very simple expressed as:
@ArchTest public static final ArchRule utilPackageDependenciesRules = classes().that().resideInAPackage("com.company.application.util") .should().onlyDependOnClassesThat().resideInAnyPackage(getAllowedDependencies("com.company.application.exception")) .andShould().onlyHaveDependentClassesThat().resideInAnyPackage("com.company.application.shoppingcart" "com.company.application.payment);So that's it. Now repeat for every package and you now have code control that runs like any other JUnit test. So therefore it will run easily as part of your CI, CD etc. Now, if you have architected your packages well, you don't have to bring up at code reviews. Instead the rules are part of your CI. As your software evolves and new packages come along and dependencies rules change, simply just change the rules that are expressed in nice fluent Java APIs. Someone new joins the teams and wants to get up to speed on the Architectural package rules? Simple, just direct them Architectural tests.
Not only does ArchUnit give you the ability to express package rules, you can also define your own rules aka conditions and then apply them to whatever code you want. For example, suppose you want a condition that an object is immutable. You naturally therefore want no setters. That could be expressed by this condition.
static ArchConditionYou could then apply the noSetter condition to any custom Exception a developer may write. It wouldn't be good if an Exception had a setter would it?noPublicSettersCondition = new ArchCondition ("class has no public setters") { @Override public void check(JavaClass item, ConditionEvents events) { for (JavaMethod javaMethod: item.getMethods()) { if (javaMethod.getName().startsWith("set") && javaMethod.getModifiers().contains(JavaModifier.PUBLIC)) { String message = String.format( "Public method %s is not allowed begin with setter", javaMethod.getName()); events.add(SimpleConditionEvent.violated(item, message)); } } } };
@ArchTest public static final ArchRule noExceptionsHaveSetters = classes().that() .areAssignableTo(RuntimeException.class).should(noSettersCondition);Suppose you keep noticing that Loggers defined in classes either aren't private, aren't static or aren't final. Don't waste time talking about it code reviews. ArchUnit it!
@ArchTest public final ArchRule loggers_should_be_private_static_final = fields().that().haveRawType(TaLogger.class) .should().bePrivate() .andShould().beStatic() .andShould().beFinal() .because("we agreed on this convention");So the goal here is to conceptualise good rules that will help your to remain testable and maintainable and then enforce them in a way that is easy to check and understand. ArchUnit really is a great library tool.