ArchUnit 実践:あなたの common パッケージは 本当に common ?


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

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

特定の業務知識をもたない汎用的な値オブジェクトクラスやユーティリティクラスを common パッケージに置いていたら1、いつのまにか common パッケージ以外に依存してしまっていたなんてことはないでしょうか?これらの汎用クラスは外部から依存されることはあっても、外部に依存してはいけないはずです。

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

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

class ArchitectureTest {

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

    @Test
    void commonパッケージは特定の業務ドメインに依存しない() {
        noClasses().that().resideInAPackage("com.example.domain.common..")
            .should()
            .dependOnClassesThat(new DescribedPredicate<>("common でないパッケージに属する") {
                /**
                 * @param clazz 依存先のクラス
                 * @return 依存先のクラスが common でないパッケージに属するクラスである場合、true
                 */
                @Override
                public boolean apply(final JavaClass clazz) {
                    if (! clazz.getPackageName().startsWith("com.example")) {
                        // サードパーティーライブラリなどへの依存はOK
                        return true;
                    }

                    return ! clazz.getPackageName().startsWith("com.example.domain.common");
                }
            })
            .check(CLASSES);
    }
}

  1. そもそも common というくくりでパッケージングすることの是非も...