You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何ArchUnit洋葱架构测试因SPI方法参数为领域模型报错?

洋葱架构ArchUnit测试报错解决方案

问题背景

我有一个严格遵循整洁/洋葱/六边形架构的演示项目,配置的ArchUnit测试代码如下:

@AnalyzeClasses(packages ="fr.tristan.demoassurance", importOptions = {ImportOption.DoNotIncludeTests.class, ExcludeConfig.class})
public class ArchitectureTest {

    @ArchTest
    public static final ArchRule onionArchitectureRule = onionArchitecture()
        .domainModels("fr.tristan.demoassurance.domain.entity..")
        .domainModels("fr.tristan.demoassurance.domain.event..")
        .domainServices("fr.tristan.demoassurance.domain.api..")
        .applicationServices("fr.tristan.demoassurance.application.controller")
        .adapter("message", "fr.tristan.demoassurance.infrastructure.message..")
        .adapter("persistence-mongodb", "fr.tristan.demoassurance.infrastructure.repository.mongodb..")
        .adapter("persistence-mysql", "fr.tristan.demoassurance.infrastructure.repository.mysql..")
        ;
}

运行测试后出现报错:

was violated (1 times):
Method <fr.tristan.demoassurance.domain.spi.message.MessageQueueSpi.send(fr.tristan.demoassurance.domain.event.PoliceAssuranceEvent)> has parameter of type <fr.tristan.demoassurance.domain.event.PoliceAssuranceEvent> in (MessageQueueSpi.java:0)

对应的问题接口代码:

package fr.tristan.demoassurance.domain.spi.message;

import fr.tristan.demoassurance.domain.event.PoliceAssuranceEvent;

public interface MessageQueueSpi {
    void send(PoliceAssuranceEvent policeAssuranceCreatedEvent);
}

我有两个困惑:

  1. 不清楚违反了哪条规则,也不明白为何SPI接口不能以领域模型作为参数。
  2. 同一父包fr.tristan.demoassurance.domain.spi下的另一个接口,其save方法同样以领域模型PoliceAssurance作为参数,但ArchUnit并未报错:
package fr.tristan.demoassurance.domain.spi.repository;

import java.util.List;

import fr.tristan.demoassurance.domain.entity.PoliceAssurance;
import fr.tristan.demoassurance.domain.exception.PoliceAssuranceException;

public interface PoliceAssuranceRepositorySpi {

    List<PoliceAssurance> findAll();
    PoliceAssurance save(PoliceAssurance policeAssurance) throws PoliceAssuranceException;
    PoliceAssurance findById(Integer id) throws PoliceAssuranceException;
    void deleteById(Integer id) throws PoliceAssuranceException;    
}

问题原因

ArchUnit的onionArchitecture()规则会根据配置的包路径划分洋葱架构层级,只有明确归类到对应层级的包,才会被允许遵循层级间的依赖规则。

你的问题核心在于:domain.spi包下的所有接口都未被配置到任何层级中,ArchUnit无法识别它们属于领域层的一部分。虽然PoliceAssuranceRepositorySpi暂时没触发报错,但这属于规则检查的遗漏,MessageQueueSpi刚好触发了非法依赖校验。

在洋葱架构中,领域SPI(端口)是领域层的核心组成部分,原本就允许直接依赖领域模型(entity、event等),但必须被归类到domainServices层级中,否则ArchUnit会将其视为未定义层级,进而触发依赖违规的判定。

解决方法

修改ArchUnit配置,将domain.spi包添加到domainServices的配置项中,让ArchUnit识别这部分接口属于领域服务层:

@AnalyzeClasses(packages ="fr.tristan.demoassurance", importOptions = {ImportOption.DoNotIncludeTests.class, ExcludeConfig.class})
public class ArchitectureTest {

    @ArchTest
    public static final ArchRule onionArchitectureRule = onionArchitecture()
        .domainModels("fr.tristan.demoassurance.domain.entity..")
        .domainModels("fr.tristan.demoassurance.domain.event..")
        // 添加domain.spi包到领域服务层
        .domainServices("fr.tristan.demoassurance.domain.api..", "fr.tristan.demoassurance.domain.spi..")
        .applicationServices("fr.tristan.demoassurance.application.controller")
        .adapter("message", "fr.tristan.demoassurance.infrastructure.message..")
        .adapter("persistence-mongodb", "fr.tristan.demoassurance.infrastructure.repository.mongodb..")
        .adapter("persistence-mysql", "fr.tristan.demoassurance.infrastructure.repository.mysql..")
        ;
}

配置完成后,domain.spi下的所有接口都会被归类为领域服务层,允许依赖最内层的领域模型,报错会消失,同时也能保证PoliceAssuranceRepositorySpi的依赖符合架构规则。

内容的提问来源于stack exchange,提问作者Tristan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 04:52:55