ArchUnit 実践:依存関係を逆転した Layered Architecture ではインフラストラクチャ層は他の層のインターフェイスにのみ依存できる


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

アーキテクチャテストのモチベーション

2 日目の ArchUnit 実践:依存関係を逆転した Layered Architecture のアーキテクチャテスト の、依存関係を逆転した Layered Architecture では、インフラストラクチャ層は依存関係の最上位に存在します。これは対象のクラス(インターフェイス)に代わって技術的詳細を実装するためであり、インフラストラクチャ層のクラスが他の層に依存してよいのは、そのインターフェイスを実装する場合のみです。他の層の具象クラスへの依存は禁止されるべきです。

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

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 static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

class ArchitectureTest {

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

    @Test
    void インフラストラクチャ層のクラスは他の層のインターフェイスにのみ依存できる() {
        classes().that().resideInAPackage("com.example.infrastructure..")
            .should()
            .onlyDependOnClassesThat(new DescribedPredicate<>("他の層のインターフェイス") {
                /**
                 * @param clazz 依存先のクラス
                 * @return 依存先のクラスが他の層の具象クラスである場合、false
                 */
                @Override
                public boolean apply(final JavaClass clazz) {
                    if (! clazz.getPackageName().startsWith("com.example")) {
                        // サードパーティーライブラリなどへの依存はOK
                        return true;
                    }
                    if (clazz.getPackageName().startsWith("com.example.infrastructure")) {
                        // 同じインフラストラクチャ層のクラスへの依存はOK
                        return true;
                    }
                    if (clazz.isInterface()) {
                        // 他の層のインターフェイスへの依存はOK
                        return true;
                    }

                    return false;
                }
            })
            .check(CLASSES);
    }
}