// 実行環境
* AdoptOpenJDK 11.0.9.1+1
* Spring Boot 2.4.0
* JUnit 5.7.0
* ArchUnit 0.14.1
アーキテクチャテストのモチベーション
ドメイン固有の例外は、そのユースケースを制御する責務をもつコントローラーやアプリケーションサービスで捕捉し、適切な後処理を行うべき。
Spring Boot でいうと、@ExceptionHandler
を付与したメソッド(大域例外ハンドラ)が、ドメイン層の例外を捕捉しないことを担保したい。
アーキテクチャテストの実装
package com.example;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.Test;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.util.List;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
class ArchitectureTest {
// 検査対象のクラス
private static final JavaClasses CLASSES =
new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.example");
@Test
void ドメイン層で発生した例外を大域例外ハンドラで捕捉しない() {
methods().that().areAnnotatedWith(ExceptionHandler.class)
.should()
.notHaveRawParameterTypes(new DescribedPredicate<>("ドメイン層の例外クラス") {
/**
* @param params `@ExceptionHandler` でアノテートされたメソッドの引数のリスト
* @return 引数にドメイン層の例外クラスを含む場合、true
*/
@Override
public boolean apply(final List<JavaClass> params) {
JavaClass exceptionClass = params.stream()
.filter(clazz -> clazz.isAssignableTo(Exception.class))
.findFirst()
.orElseThrow();
return exceptionClass.getPackageName().startsWith("com.example.domain");
}
})
.check(CLASSES);
}
}