Architecture validation: ArchUnit vs. stereotype check

Recently a colleague asked me if I know ArchUnit as architecture validation tool und how does it compare to our stereotype check. Here is what I think about it. Please have a look at the project sites for detailed informations.
Compare the approaches
ArchUnit has the same goal as stereotype check, but a different approach: Giving the developer fast feedback, if her/his work conforms to the architecture. Using ArchUnit you have to implement a UnitTest that checks all your classes against a ruleset implemented in Java. Using stereotype check you have to configure a checkstyle plugin with a xml file. The benefit of the checkstyle approach is that the feedback is given directly while typing. The unit test approach might find architecture problems at the end your development, when you execute all unit tests before you commit your changes. To solve these problems you might have to refactor your whole change. But getting feedback before committing the changes, is better than after commit. Empirically a architecture problem that is committed, will stay in the code base for a long time.
Both approaches support a central definition of the checks, which can be modified by a project specific check definition. A architecture team can provide a standard check definition, which can be extended by the team implementing a part of the application.
But ArchUnit has some clear benefits. ArchUnit uses java to implement the checks instead of xml. For java developers this is much more easier and writing unit tests is what java developers should do every day. Using stereotype check you are restricted to checks defined by its xsd. In java you can add additional checks easily.
Because ArchUnit checks are implemented against the bytecode, checks can be implemented for classes which are not in the source path. Stereotype check as checkstyle plugin only sees the source code of one class. This can be a problem, when your check needs information about another class which is not in your source path.
Compare some checks
To compare the different approaches in detail I implemented some checks with ArchUnit for a project in that I currently use stereotype check to see if the same checks can be implemented.
Check dependencies between classes by package
In stereotype check you have to define 2 stereotypes and a dependency to allow the use. All others disallowed
1 2 3 4 5 6 7 8 9 |
<dependency from "api" to "business"/> <stereotype id="api"> <package name="^([a-z]+[a-z0-9\.]*)\.api(\.[a-z][a-z0-9]*)*$" condition="sufficient" /> </stereotype> <stereotype id="business"> <package name="^([a-z]+[a-z0-9\.]*)\.business(\.[a-z][a-z0-9]*)*$" condition="sufficient" /> </stereotype> |
The same in ArchUnit
1 2 3 4 5 6 7 8 |
@ArchTest public static void businessShouldNotUseApi(JavaClasses classes) { ArchRule rule = noClasses().that().resideInAPackage("..business..") .should().accessClassesThat() .resideInAPackage("..api..") .because("The business layer should not use the api layer "); rule.check(classes); } |
Check dependencies between classes by postfix
1 2 3 4 5 6 |
<dependency from="dto" to="dto" /> <stereotype id="dto"> <postfix name="Dto" condition="sufficient" /> <package name="^([a-z]+[a-z0-9\.]*)\.api(\.[a-z][a-z0-9]*)*$" condition="necessary" /> </stereotype> |
1 2 3 4 5 6 7 |
@ArchTest public static void dtosShouldOnlyUseOtherDtos(JavaClasses classes) { ArchRule rule = classes().that().haveNameMatching(".*Dto") .should().accessClassesThat().haveNameMatching(".*Dto") .because("The dto stereotype should use onlöy other dtos."); rule.check(classes); } |
Class with a postfix must be in specific package
1 2 3 4 5 |
<stereotype id="dto"> <postfix name="Dto" condition="sufficient" /> <package name="^([a-z]+[a-z0-9\.]*)\.api(\.[a-z][a-z0-9]*)*$" condition="necessary" /> </stereotype> |
1 2 3 4 5 6 7 |
@ArchTest public static void dtoStereotypeIsInPackageApi(JavaClasses classes) { ArchRule rule = classes().that().haveNameMatching(".*Dto") .should().resideInAPackage("..api..") .because("The dto stereotype can only be defined in layer api."); rule.check(classes); } |
Class with a postfix should be in a package and has a annotation
1 2 3 4 5 6 7 8 |
<stereotype id="entity"> <postfix name="Entity" condition="sufficient" /> <annotation condition="sufficient"> <annotationname name="javax.persistence.Entity" /> </annotation> <package name="^([a-z]+[a-z0-9\.]*)\.persistence(\.[a-z][a-z0-9]*)*$" condition="necessary" /> </stereotype> |
1 2 3 4 5 6 7 8 |
@ArchTest public static void entityStereotypeIsInPackageApiAndHasAnnotationEntity(JavaClasses classes) { ArchRule rule = classes().that().haveNameMatching(".*Entity") .should().resideInAPackage("..persistence..") .andShould().beAnnotatedWith(Entity.class) .because("The entity stereotype can only defined in layer persistence und must have the Annotation javax.persistence.Entity."); rule.check(classes); } |
Class with a postfix should inherit from a base class
1 2 3 4 5 6 7 |
<stereotype id="transformer"> <postfix name="Tf" condition="sufficient" /> <annotation name="org.springframework.stereotype.Component" condition="necessary" /> <baseclass name="AbstractTf" condition="necessary" /> <package name=".*" condition="necessary" /> </stereotype> |
1 2 3 4 5 6 7 8 9 |
@ArchTest public static void transformerStereotypeIsAComponentThatIsInheritedFromAbstractTf(JavaClasses classes) { ArchRule rule = classes().that().haveNameMatching(".*Tf").and().dontHaveModifier(JavaModifier.ABSTRACT) .should().beAnnotatedWith(Component.class) .andShould().beAssignableTo(AbstractTf.class) .because("The transformer stereotype must have the Annotation org.springframework.stereotype.Component" +"and must be inherited from AbstractTf."); rule.check(classes); } |
Conclusion
The answer to the question of my colleague is that it depends on your needs and preferences. But the information above may help you to decide.
Recent posts






Comment article