基于ArchUnit实现六边形架构跨域访问规则的问题排查
六边形架构跨域访问规则的ArchUnit实现问题
背景
我们正在将代码重构为六边形架构,计划将每个领域拆分为独立Gradle模块。目前所有代码集中在单个模块内,各领域包可自由交互。我们希望先在包层面确立架构边界,再拆分模块,因此采用ArchUnit强制架构规范。
已实现的域内分层规则
已通过以下代码完成每个域内的分层架构校验:
@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))); }
待实现的跨域规则及问题
需要实现跨域访问规则:领域A的包不得访问领域B,仅领域A的outgoing infra子包可访问领域B的api子包。尝试编写的测试代码无法捕获违规情况:
@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); }
注:已添加辅助静态方法处理子包,
getPackagesOtherThan(module)返回其他领域的包列表。曾参考StackOverflow相关问题,但场景不匹配。
解决方案
问题核心在于规则过滤逻辑不精准、校验方向错误,修正后的代码如下:
@ParameterizedTest(name = "Module {0} should only depend on itself, except outgoing infra on other apis") @MethodSource("provideDomains") void domainModuleBoundariesAreRespected(String module) { // 规则1:当前模块除outgoing infra外,其他包禁止访问任何其他领域的包 noClasses() .that().resideInAPackage(includeSubPackages(toPackage(module))) .and().doNotResideInAPackage(outGoingInfra(toPackage(module))) .should().dependOnClassesThat().resideInAnyPackage(includeSubPackages(getPackagesOtherThan(module))) .allowEmptyShould(true) .check(new ClassFileImporter().importPackages(ROOT_DOTDOT)); // 规则2:当前模块的outgoing infra仅允许访问自身所有包、其他领域的api包 classes() .that().resideInAPackage(outGoingInfra(toPackage(module))) .should().onlyDependOnClassesThat() .resideInAnyPackage( includeSubPackages(toPackage(module)), toApi(getPackagesOtherThan(module)), includeSubPackages(ROOT_DOT + "vocabulary") // 若有全局词汇包,可追加 ) .allowEmptyShould(true) .check(new ClassFileImporter().importPackages(ROOT_DOTDOT)); }
关键修正点
- 明确排除合法包:在规则1中直接通过
doNotResideInAPackage排除当前模块的outgoing infra,避免误判合法访问。 - 全量扫描依赖:将扫描范围改为
ROOT_DOTDOT,确保跨域依赖被完整检测。 - 修正校验方向:规则2从“必须访问其他api”改为“仅能访问指定合法包”,用
onlyDependOnClassesThat精准限制依赖范围。 - 补充自身访问权限:允许
outgoing infra访问自身模块的所有分层(如domain、vocabulary等)。
内容的提问来源于stack exchange,提问作者Harm De Weirdt
相关产品推荐
相关产品推荐

