// 実行環境
* AdoptOpenJDK 11.0.9.1+1
* JUnit 5.7.0
* ArchUnit 0.14.1
レイヤーの依存関係
1日目の ArchUnit 実践:Layered Architecture のアーキテクチャテスト の Layered Architecture と比較すると、依存関係が逆転しており、インフラストラクチャ層より下位の層は技術的詳細と疎になる。また、ドメイン層がどの層にも依存しないため、ドメイン層はビジネスのコアロジックの表現により専念できる。
Java プロジェクトのパッケージ構成
アーキテクチャテストの実装
package com.example;
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 static com.tngtech.archunit.library.Architectures.layeredArchitecture;
class ArchitectureTest {
// 検査対象のクラス
private static final JavaClasses CLASSES =
new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("com.example");
@Test
void 依存関係を逆転したレイヤードアーキテクチャのアーキテクチャテスト() {
layeredArchitecture()
.layer("ui").definedBy("com.example.presentation..")
.layer("app").definedBy("com.example.application..")
.layer("domain").definedBy("com.example.domain..")
.layer("infra").definedBy("com.example.infrastructure..")
// UI 層はインフラストラクチャ層からのみ依存される
.whereLayer("ui").mayOnlyBeAccessedByLayers("infra")
// アプリケーション層はインフラストラクチャ層と UI 層からのみ依存される
.whereLayer("app").mayOnlyBeAccessedByLayers("infra", "ui")
// ドメイン層はインフラストラクチャ層とアプリケーション層からのみ依存される
.whereLayer("domain").mayOnlyBeAccessedByLayers("infra", "app")
// インフラストラクチャ層はどの層からも依存されない
.whereLayer("infra").mayNotBeAccessedByAnyLayer()
.check(CLASSES);
}
}
アーキテクチャテストの実行例(テスト失敗例)
ドメイン層の Service クラスが、インフラストラクチャ層の Repository クラスに依存してしまっている、というアーキテクチャ違反を検知した想定でのテスト失敗例。
依存関係逆転の原則を適用するのであれば、
- ドメイン層は Repository インターフェイスを公開し、それをインフラストラクチャ層で実装する
- ドメイン層の Service クラスは Repository インターフェイスに依存し、Repository の実装クラスは DI により注入される
ことが期待される。
$ ./gradlew clean check
> Task :test
ArchitectureTest > 依存関係を逆転したレイヤードアーキテクチャのアーキテクチャテスト() FAILED
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Layered architecture consisting of
layer 'ui' ('com.example.presentation..')
layer 'app' ('com.example.application..')
layer 'domain' ('com.example.domain..')
layer 'infra' ('com.example.infrastructure..')
where layer 'ui' may only be accessed by layers ['infra']
where layer 'app' may only be accessed by layers ['infra', 'ui']
where layer 'domain' may only be accessed by layers ['infra', 'app']
where layer 'infra' may not be accessed by any layer' was violated (2 times):
Constructor <com.example.domain.employee.EmployeeService.<init>(com.example.infrastructure.datasource.EmployeeRepository)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepository> in (EmployeeService.java:0)
Field <com.example.domain.employee.EmployeeService.employeeRepository> has type <com.example.infrastructure.datasource.EmployeeRepository> in (EmployeeService.java:0)
at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:94)
at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:82)
at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:267)
at com.example.ArchitectureTest.依存関係を逆転したレイヤードアーキテクチャのアーキテクチャテスト(ArchitectureTest.java:89)
1 test completed, 1 failed
> Task :test FAILED