0

0

使用ArchUnit强制执行单一依赖:服务与存储库的架构约束

聖光之護

聖光之護

发布时间:2025-11-09 14:38:00

|

765人浏览过

|

来源于php中文网

原创

使用ArchUnit强制执行单一依赖:服务与存储库的架构约束

本文将详细介绍如何使用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.. 包中。这确保了存储库不会被服务层以外的组件直接访问。然而,这条规则并没有限制一个存储库可以被多少个服务使用,它仅仅确保了依赖者是服务。

强制单一服务依赖:使用自定义ArchCondition

为了实现“一个存储库只能被一个服务使用”的严格约束,我们需要检查每个存储库类的直接依赖者数量。ArchUnit提供了 ArchCondition 机制,允许我们定义复杂的自定义检查逻辑。

方案一:简洁的Lambda表达式与`describe`

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
            )));
}

在这段代码中:

OpenArt
OpenArt

在线AI绘画艺术图片生成器工具

下载
  • classes().that().resideInAnyPackage(SUBPACKAGE_NAME_REPOSITORY) 选择了所有存储库类作为规则检查的对象。
  • .should(have(describe(...))) 应用了一个自定义条件。
  • describe("#{dependent classes} == 1", ...) 定义了一个描述性谓词。其中的字符串是当规则通过时显示的描述,而Lambda表达式 javaClass -> ... 则是实际的检查逻辑。
  • javaClass.getDirectDependenciesToSelf() 获取了所有直接依赖于当前 javaClass 的依赖关系。
  • .stream().map(Dependency::getOriginClass) 将这些依赖关系映射到它们的源类(即依赖者类)。
  • .count() == 1 检查依赖者的数量是否恰好为1。

这个方案简洁有效,但当规则被违反时,生成的错误消息可能不够详细,仅显示“dependent classes == 1”的条件未满足。

方案二:自定义ArchCondition以生成更友好的违规消息

为了提供更具体、更易于理解的违规消息,我们可以实现一个完整的 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("have one dependent class") {
                @Override
                public void check(JavaClass javaClass, ConditionEvents events) {
                    // 获取所有直接依赖于当前存储库类的类
                    Set 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 的实现提供了以下优点:

  • 清晰的描述: new ArchCondition("have one dependent class") 定义了规则的意图。
  • check 方法: 这是核心逻辑所在,它接收 JavaClass(当前正在检查的类)和 ConditionEvents(用于报告违规)。
  • 获取依赖者: 同样使用 javaClass.getDirectDependenciesToSelf().stream().map(Dependency::getOriginClass).collect(toSet()) 来获取所有依赖于当前存储库类的集合。
  • 详细的违规消息:
    • 如果 dependentClasses 为空,则报告“has no dependent classes”。
    • 如果 dependentClasses 大于1,则列出所有依赖者的全限定名,例如“has several dependent classes: com.example.service.UserService, com.example.service.ProductService”。
  • violated 和 createMessage: 这些方法用于向 ConditionEvents 报告具体的违规事件和消息。

通过这种方式,当ArchUnit测试失败时,开发者可以立即看到是哪个存储库类违反了规则,以及具体被哪些服务类共享,从而大大提高了问题排查的效率。

注意事项与最佳实践

  1. 包名约定: 示例中使用了 SUBPACKAGE_NAME_REPOSITORY 和 SUBPACKAGE_NAME_SERVICE 等常量来定义包路径。在实际项目中,应确保这些包名能够准确地匹配您的项目结构,并且具有良好的命名约定。
  2. getDirectDependenciesToSelf() 的理解: 这个方法返回的是所有直接依赖于当前 JavaClass 的 Dependency 对象。每个 Dependency 对象包含了依赖的源(getOriginClass())和目标(getTargetClass()),以及依赖的类型。
  3. 测试覆盖: 除了编写这些ArchUnit规则,还应确保这些规则被集成到CI/CD流程中,以便在代码提交时自动验证架构合规性。
  4. 规则粒度: 根据项目需求,您可以调整规则的粒度。例如,如果某些存储库确实需要被多个服务共享(例如,通用的用户认证存储库),则可能需要为这些特殊情况定义豁免规则,或者将它们放置在不同的包中,以便不被此规则约束。
  5. 可读性: 编写清晰的ArchUnit规则描述和详细的违规消息对于团队协作和长期维护至关重要。

总结

通过ArchUnit的自定义 ArchCondition 机制,我们可以灵活且精确地定义复杂的架构规则,例如强制存储库只能被单一服务依赖。这不仅有助于在开发早期发现架构问题,还能在项目演进过程中持续维护架构的健康性。无论是使用简洁的 describe 方法,还是实现更详细的 ArchCondition 来生成丰富的违规消息,ArchUnit都为Java项目的架构治理提供了强大的工具。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

835

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

740

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

736

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

399

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16926

2023.08.03

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

43

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.6万人学习

C# 教程
C# 教程

共94课时 | 6.9万人学习

Java 教程
Java 教程

共578课时 | 47.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号