
本文将详细介绍如何使用archunit定义并强制执行一项架构规则:确保每个存储库(repository)类只能被一个服务(service)类所依赖。我们将探讨如何通过自定义archcondition来精确检查依赖数量,并生成清晰的违规消息,从而有效维护应用模块间的单一职责和解耦性。
在许多分层架构中,服务层(Service Layer)和数据访问层(Repository Layer)是常见的组件。通常,一个服务可以依赖多个存储库来完成其业务逻辑,但为了保持架构的清晰性、可维护性和单一职责原则,有时需要强制规定一个存储库只能被一个特定的服务所使用,即不允许存储库在多个服务之间共享。这种约束有助于避免复杂的交叉依赖和潜在的副作用。
ArchUnit是一个强大的Java架构测试库,它允许开发者以代码的形式定义和验证架构规则。接下来,我们将探讨如何使用ArchUnit来实现“存储库只能被一个服务使用”这一特定的架构规则。
在强制单一服务依赖之前,一个更基础的规则是确保存储库类仅被服务层中的类所依赖,而不是被其他层(如控制器层或工具类)直接依赖。这可以通过以下ArchUnit规则实现:
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
public class RepositoryServiceRules {
// 假设这些是定义包名的常量
private static final String SUBPACKAGE_NAME_REPOSITORY = "..repository..";
private static final String SUBPACKAGE_NAME_SERVICE = "..service..";
@ArchTest
static final ArchRule repository_must_only_be_used_by_a_service =
classes().that().resideInAnyPackage(SUBPACKAGE_NAME_REPOSITORY)
.should().onlyHaveDependentClassesThat()
.resideInAnyPackage(SUBPACKAGE_NAME_SERVICE);
}这条规则定义了:所有位于 ..repository.. 包中的类,它们的所有依赖者(即使用它们的类)都必须位于 ..service.. 包中。这确保了存储库不会被服务层以外的组件直接访问。然而,这条规则并没有限制一个存储库可以被多少个服务使用,它仅仅确保了依赖者是服务。
为了实现“一个存储库只能被一个服务使用”的严格约束,我们需要检查每个存储库类的直接依赖者数量。ArchUnit提供了 ArchCondition 机制,允许我们定义复杂的自定义检查逻辑。
ArchUnit允许使用 describe 方法结合Lambda表达式快速定义一个简单的 ArchCondition。这种方式适用于逻辑相对简单,且默认违规消息可以接受的情况。
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.Dependency;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.base.DescribedPredicate.describe;
import static com.tngtech.archunit.lang.conditions.ArchConditions.have;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
public class RepositoryServiceRules {
private static final String SUBPACKAGE_NAME_REPOSITORY = "..repository..";
private static final String SUBPACKAGE_NAME_SERVICE = "..service..";
@ArchTest
ArchRule repository_must_have_exactly_one_dependent_class =
classes().that().resideInAnyPackage(SUBPACKAGE_NAME_REPOSITORY)
.should(have(describe("#{dependent classes} == 1", javaClass ->
javaClass.getDirectDependenciesToSelf().stream()
.map(Dependency::getOriginClass).count() == 1
)));
}在这段代码中:
这个方案简洁有效,但当规则被违反时,生成的错误消息可能不够详细,仅显示“dependent classes == 1”的条件未满足。
为了提供更具体、更易于理解的违规消息,我们可以实现一个完整的 ArchCondition。这在大型项目或复杂规则中尤其有用,因为它能帮助开发者快速定位问题。
import com.tngtech.archunit.core.domain.Dependency;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;
import java.util.Set;
import java.util.stream.Collectors;
import static com.tngtech.archunit.lang.ConditionEvent.createMessage;
import static com.tngtech.archunit.lang.SimpleConditionEvent.violated;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
public class RepositoryServiceRules {
private static final String SUBPACKAGE_NAME_REPOSITORY = "..repository..";
private static final String SUBPACKAGE_NAME_SERVICE = "..service..";
@ArchTest
ArchRule repository_must_have_exactly_one_dependent_class_with_custom_message =
classes().that().resideInAnyPackage(SUBPACKAGE_NAME_REPOSITORY)
.should(new ArchCondition<JavaClass>("have one dependent class") {
@Override
public void check(JavaClass javaClass, ConditionEvents events) {
// 获取所有直接依赖于当前存储库类的类
Set<JavaClass> dependentClasses =
javaClass.getDirectDependenciesToSelf().stream()
.map(Dependency::getOriginClass)
.collect(toSet());
// 检查依赖者数量是否不为1
if (dependentClasses.size() != 1) {
String message;
if (dependentClasses.isEmpty()) {
// 如果没有依赖者
message = "has no dependent classes";
} else {
// 如果有多个依赖者,列出它们
message = dependentClasses.stream()
.map(JavaClass::getName)
.collect(joining(", ", "has several dependent classes: ", ""));
}
// 报告违规,并附带详细消息
events.add(violated(javaClass, createMessage(javaClass, message)));
}
}
});
}这个自定义 ArchCondition 的实现提供了以下优点:
通过这种方式,当ArchUnit测试失败时,开发者可以立即看到是哪个存储库类违反了规则,以及具体被哪些服务类共享,从而大大提高了问题排查的效率。
通过ArchUnit的自定义 ArchCondition 机制,我们可以灵活且精确地定义复杂的架构规则,例如强制存储库只能被单一服务依赖。这不仅有助于在开发早期发现架构问题,还能在项目演进过程中持续维护架构的健康性。无论是使用简洁的 describe 方法,还是实现更详细的 ArchCondition 来生成丰富的违规消息,ArchUnit都为Java项目的架构治理提供了强大的工具。
以上就是使用ArchUnit强制执行单一依赖:服务与存储库的架构约束的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号