文章首发公众号『风象南』
之前分享了一篇关于『Java项目工程分包』设计的文章,更近一步,随着代码规模扩张和模块复杂度提升,系统架构的腐化风险显著增加,如何持续保证系统整体代码结构的清晰与整洁,传统靠人工代码审查难以有效应对分层混乱、循环依赖、职责扩散等架构问题。
ArchUnit 作为轻量级架构治理工具,通过规则引擎和自动化检测机制,可以提供对代码结构和依赖关系的全面分析。它能够帮助开发团队快速发现潜在的架构违规问题,并在早期阶段进行修正,从而有效提升代码质量和系统可维护性。
ArchUnit 是什么
ArchUnit 是一个专为 Java 开发者打造的架构测试框架。它允许我们以代码的方式定义和验证 Java 项目的架构规则,确保代码库在不断演进的过程中始终遵循预先设定的架构规则。
ArchUnit 能做什么
1. 定义和验证分层架构规则
在常见的 Java 项目中,分层架构如表现层、业务逻辑层和数据访问层是非常普遍的。使用 ArchUnit,我们可以轻松定义各层之间的访问规则。
例如,规定表现层只能调用业务逻辑层的接口,而业务逻辑层不能直接访问数据访问层的实现类。
通过这样的规则定义,ArchUnit 能够在测试过程中检查代码是否严格遵守了分层架构的约束,防止层与层之间的非法依赖,从而保持架构的清晰性和稳定性。
2. 确保依赖关系的合理性
项目中类与类之间的依赖关系错综复杂,如果不合理控制,很容易形成难以维护的依赖网。ArchUnit 可以帮助我们定义和验证依赖规则。
比如,限制某个模块不能依赖特定的其他模块,或者确保某些核心模块只能被特定的模块依赖。这样能够有效避免不必要的依赖,减少模块之间的耦合度,提高代码的可维护性和可扩展性。
3. 验证代码结构规范
除了架构层面的规则,ArchUnit 还能用于验证代码的结构规范。例如,我们可以规定所有的实体类必须位于特定的包下,或者所有的服务类必须实现特定的接口。
ArchUnit 的优势
1. 无缝集成到测试流程
ArchUnit 与主流的 Java 测试框架如 JUnit、TestNG 具备较好的兼容性。可以像编写普通单元测试一样,将架构测试用例融入到现有的测试套件中。
2. 简洁易用的 DSL
ArchUnit 提供了一种简洁且易于理解的领域特定语言(DSL)来定义架构规则。能快速上手,根据项目的需求编写合适的架构测试用例。
3. 强大的灵活性
ArchUnit支持各种复杂的架构场景,可以根据项目的具体需求定制不同层次的架构规则,从基本的包依赖规则到复杂的类关系约束,可以根据需要灵活配置。
如何在项目中使用 ArchUnit
在项目的构建文件(如 Maven 的 pom.xml 或 Gradle 的 build.gradle)中添加 ArchUnit 的依赖。以 Maven 为例
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>0.21.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit</artifactId>
<version>0.21.0</version>
<scope>test</scope>
</dependency>
1. 编写架构测试用例
根据项目的架构设计,使用 ArchUnit 的 DSL 编写相应的架构测试方法。
3.1 验证分层架构
表现层不应直接调用数据访问层
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
public class LayeredArchitectureTest {
@Test
public void testLayeredArchitecture() {
JavaClasses classes = new ClassFileImporter().importPackages("com.example.project");
ArchRule rule = noClasses()
.that().areInPackage("com.example.project.presentation")
.should().callClassesThat().areInPackage("com.example.project.dataaccess");
rule.check(classes);
}
}
3.2 检查依赖关系
禁止 moduleA 中的类直接依赖 moduleC 中的类
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
public class DependencyRuleTest {
@Test
public void testDependencyRule() {
JavaClasses classes = new ClassFileImporter().importPackages("com.example.project");
ArchRule rule = noClasses()
.that().areInPackage("com.example.project.moduleA")
.should().dependOnClassesThat().areInPackage("com.example.project.moduleC");
rule.check(classes);
}
}
3.2 验证代码结构
强制所有以 Entity 结尾的类必须放在 entities 包中
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
public class CodeStructureTest {
@Test
public void testCodeStructure() {
JavaClasses classes = new ClassFileImporter().importPackages("com.example.project");
ArchRule rule = classes()
.that().haveSimpleNameEndingWith("Entity")
.should().beInPackage("com.example.project.entities");
rule.check(classes);
}
}
2. 执行架构测试
在项目构建过程中,运行测试任务(如 Maven 的mvn test命令或 Gradle 的gradle test命令),ArchUnit 会自动执行编写好的架构测试用例。
如果发现有违反架构规则的情况,测试将会失败,并给出详细的错误信息。
总结
ArchUnit 提供了一种强大而有效的方式来确保项目架构的质量和稳定性。
通过定义和验证架构规则,它能够在项目体积不断增长的过程中,及时发现潜在的架构问题,避免代码陷入混乱。