LoginSignup
0
0

More than 3 years have passed since last update.

ArchUnit 実践:依存関係を逆転した Layered Architecture のアーキテクチャテスト

Last updated at Posted at 2020-12-02
// 実行環境
* AdoptOpenJDK 11.0.9.1+1
* JUnit 5.7.0
* ArchUnit 0.14.1

レイヤーの依存関係

1日目の ArchUnit 実践:Layered Architecture のアーキテクチャテスト の Layered Architecture と比較すると、依存関係が逆転しており、インフラストラクチャ層より下位の層は技術的詳細と疎になる。また、ドメイン層がどの層にも依存しないため、ドメイン層はビジネスのコアロジックの表現により専念できる。

image.png

Java プロジェクトのパッケージ構成

image.png

アーキテクチャテストの実装

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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0