ArchUnit 実践:Onion Architecture のアーキテクチャテスト


// 実行環境
* AdoptOpenJDK 11.0.9.1+1
* JUnit 5.7.0
* ArchUnit 0.14.1

レイヤーの依存関係

  • 依存の方向は外側の層から内側の層への一方通行
  • 一番外側のアダプター同士は依存しない

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.onionArchitecture;

class ArchitectureTest {

    // 検査対象のクラス
    private static final JavaClasses CLASSES =
            new ClassFileImporter()
                    .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                    .importPackages("com.example");

    @Test
    void オニオンアーキテクチャのアーキテクチャテスト() {
        onionArchitecture()
            // domain.model パッケージをドメインモデル層として定義
            .domainModels("com.example.domain.model..")
            // domain.service パッケージをドメインサービス層として定義
            .domainServices("com.example.domain.service..")
            // application パッケージをアプリケーションサービス層として定義
            .applicationServices("com.example.application..")

            // infrastructure パッケージをインフラストラクチャ・アダプターとして定義
            .adapter("infra", "com.example.infrastructure..")
            // presentation パッケージをユーザインターフェイス・アダプターとして定義
            .adapter("ui", "com.example.presentation..")

            .check(CLASSES);
    }
}

アーキテクチャテストの実行例

テスト失敗例①(ドメインサービス→アプリケーションサービスへの依存)

内側のドメインサービス層の Service クラスが、外側のアプリケーションサービス層の UseCase クラスに依存してしまっている、というアーキテクチャ違反を検知した想定でのテスト失敗例。

$ ./gradlew clean check

> Task :test FAILED

ArchitectureTest > オニオンアーキテクチャのアーキテクチャテスト() FAILED
    java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Onion architecture consisting of
    domain models ('com.example.domain.model..')
    domain services ('com.example.domain.service..')
    application services ('com.example.application..')
    adapter 'infra' ('com.example.infrastructure..')
    adapter 'ui' ('com.example.presentation..')' was violated (2 times):
    Constructor <com.example.domain.service.employee.EmployeeRegisterService.<init>(com.example.application.HogeUseCase)> has parameter of type <com.example.application.HogeUseCase> in (EmployeeRegisterService.java:0)
    Field <com.example.domain.service.employee.EmployeeRegisterService.hogeUseCase> has type <com.example.application.HogeUseCase> in (EmployeeRegisterService.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.tngtech.archunit.library.Architectures$OnionArchitecture.check(Architectures.java:538)
        at com.example.ArchitectureTest.オニオンアーキテクチャのアーキテクチャテスト(ArchitectureTest.java:560)

1 test completed, 1 failed

テスト失敗例②(ユーザインターフェイス・アダプター→インフラストラクチャ・アダプターへの依存)

ユーザインターフェイス・アダプターの Controller クラスが、インフラストラクチャ・アダプターの Repository(Impl) クラスに依存してしまっている、というアーキテクチャ違反を検知した想定でのテスト失敗例。

$ ./gradlew clean check

> Task :test FAILED

ArchitectureTest > オニオンアーキテクチャのアーキテクチャテスト() FAILED
    java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Onion architecture consisting of
    domain models ('com.example.domain.model..')
    domain services ('com.example.domain.service..')
    application services ('com.example.application..')
    adapter 'infra' ('com.example.infrastructure..')
    adapter 'ui' ('com.example.presentation..')' was violated (2 times):
    Constructor <com.example.presentation.employee.EmployeeController.<init>(com.example.infrastructure.datasource.EmployeeRepositoryImpl)> has parameter of type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeController.java:0)
    Field <com.example.presentation.employee.EmployeeController.employeeRepository> has type <com.example.infrastructure.datasource.EmployeeRepositoryImpl> in (EmployeeController.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.tngtech.archunit.library.Architectures$OnionArchitecture.check(Architectures.java:538)
        at com.example.ArchitectureTest.オニオンアーキテクチャのアーキテクチャテスト(ArchitectureTest.java:560)

1 test completed, 1 failed