We are in the process of refactoring our code to a hexagonal architecture, with each domain in the code being a separate gradle module. Currently all the code is one module with a package for each domain, and modules interacting with eachother freely. We want to first get the architectural boundaries correct on the package level before extracting each domain package to a domain module. To do this we want to enforce the architecture using archunit.
Inside each domain package, I've reconstructed the rules of our new architecture using layers:
@ParameterizedTest(name = "The hex architecture should be respected in module {0}.")
@MethodSource("provideDomains")
void hexArchRespectedInEveryModule(String pkg) {
layeredArchitecture()
.consideringOnlyDependenciesInAnyPackage(ROOT_DOTDOT)
.withOptionalLayers(true)
.layer("api").definedBy(ROOT_DOT + pkg + ".api..")
.layer("usecases").definedBy(ROOT_DOT + pkg + ".usecases..")
.layer("domain").definedBy(ROOT_DOT + pkg + ".domain..")
.layer("incoming infra").definedBy(ROOT_DOT + pkg + ".infrastructure.incoming..")
.layer("outgoing infra").definedBy(ROOT_DOT + pkg + ".infrastructure.outgoing..")
.layer("vocabulary").definedBy(ROOT_DOT + pkg + ".vocabulary..")
.whereLayer("incoming infra").mayOnlyAccessLayers("api", "vocabulary")
.whereLayer("usecases").mayOnlyAccessLayers("api", "domain", "vocabulary")
.whereLayer("domain").mayOnlyAccessLayers("domain", "vocabulary")
.whereLayer("outgoing infra").mayOnlyAccessLayers("domain", "vocabulary")
.check(new ClassFileImporter().importPackages(toPackage(pkg)));
}
I am however failing to write the check that a domain can only access another domain by calling it from an outgoing infra adapter, using the API provided by the other domain. For that case I'd have to write something that says: "no packages in domain A may call domain B, except for the packages in outgoing infra of A that can call the API subpackage in B.
I've tried several different variations, eventually thinking that this would be correct, but violations still don't seem to get caught:
@ParameterizedTest(name = "Module {0} should only depend on itself, except outgoing infra on other apis")
@MethodSource("provideDomains")
void domainModuleBoundariesAreRespected(String module) {
noClasses()
.that().resideInAPackage(includeSubPackages(toPackage(module)))
.should().dependOnClassesThat().resideInAnyPackage(includeSubPackages(getPackagesOtherThan(module)))
.allowEmptyShould(true)
.check(allButOutgoingInfra);
classes()
.that().resideInAPackage(outGoingInfra(toPackage(module)))
.should().dependOnClassesThat().resideInAnyPackage(toApi(getPackagesOtherThan(module)))
.allowEmptyShould(true)
.check(allClasses);
}
There's some helper static methods I've added that add the ".." to get subpackages, or add a suffix to a package to indicate a subpackage. "getPackagesOtherThan(module)" returns a list of the other domains' packages.
I found this question but my problem seems different because I have two subpackages in relation to eachother, not just one.