ArchUnit使い方メモ

# ArchUnit とは
- Java プログラムのアーキテクチャをユニットテストするためのライブラリ
- パッケージやクラス、レイヤ間の依存関係をチェックしたり、循環参照をチェックしたりできる
- 読みは「あーきゆにっと」

# 環境
## Java
```
>java -version
openjdk version "11.0.8" 2020-07-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.8+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.8+10, mixed mode)
```

## OS
Windows 10 Home 64bit

## JUnit
```gradle:build.gradle
dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter:5.7.0"
    ...
}
```

# Hello World
## 実装
```text:フォルダ構成
|-build.gradle
`-src/
  |-main/java/
  | `-example/
  |   |-controller/
  |   | `-FooController.java
  |   `-service/
  |     `-FooService.java
  |
  `-test/java/
    `-example/
      `-ArchUnitTest.java
```

```gradle:build.gradle
plugins {
    id "java"
}

sourceCompatibility = 11
targetCompatibility = 11

[compileJava, compileTestJava]*.options*.encoding = "UTF-8"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter:5.7.0"
    testImplementation "com.tngtech.archunit:archunit-junit5:0.14.1"
}

test {
    useJUnitPlatform()
}
```

```java:FooService.java
package example.service;

import example.controller.FooController;

public class FooService {
    // Service が Controller に依存した状態になっている
    FooController controller;
}
```

```java:ArchUnitTest.java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example");

        ArchRule rule = noClasses().that().resideInAPackage("..service..")
                        .should().dependOnClassesThat().resideInAPackage("..controller..");
        
        rule.check(javaClasses);
    }
}
```

## 実行結果
```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package '..service..' should depend on classes that reside in a package '..controller..'' was violated (1 times):
Field <example.service.FooService.controller> has type <example.controller.FooController> in (FooService.java:0)
```

## 説明
### 依存関係
```gradle:build.gradle
dependencies {
    ...
    testImplementation "com.tngtech.archunit:archunit-junit5:0.14.1"
}
```

- JUnit5 で ArchUnit を使う場合は、 `com.tngtech.archunit:archunit-junit5` を依存関係に追加する

### クラスをインポートする
```java:ArchUnitTest.java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
...
import org.junit.jupiter.api.Test;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example");
        ...
    }
}
```

- ArchUnit のテストは、まずクラスをインポートすることから始める
- インポートには `ClassFileImporter` を使用する
- 上記例の `importPackages(String)` は、指定したパッケージ以下のクラスを再帰的に読み込んで `JavaClasses` 型で返している
    - `JavaClasses` には、読み込まれたクラスが含まれている

### アーキテクチャのルールを定義する
```java:ArchUnitTest.java
package example;

...
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        ...
        ArchRule rule = noClasses().that().resideInAPackage("..service..")
                        .should().dependOnClassesThat().resideInAPackage("..controller..");
        ...
    }
}
```

- アーキテクチャのルール（検証内容）は、 `ArchRule` という型で定義する
- `ArchRule` を作るための API として、 `ArchRuleDefinition` に静的なファクトリメソッドが用意されている
    - ここでは `noClasses()` で `ArchRule` の定義を開始している
- `ArchRule` の定義は流れるようなインタフェースで構築できるようになっている
    - `noClasses()` は、このあとに続くルールに該当するクラスが存在しないことを定義している
    - `resideInAPackage(String)` は、パッケージの範囲を絞り込んでいる
        - ２つのドット(`..`)は、任意のサブパッケージを表している
        - したがって `..service..` は、すべての `service` という名前のパッケージ以下の、すべてのサブパッケージを対象としている
    - `dependOnClassesThat()` は、 `should()` より前で指定した対象が依存する先を定義している
        - `.should().dependOnClassesThat().resideInAPackage("..controller..")` は、 `controller` パッケージ以下のクラスに依存しなければならないことを宣言している
    - 以上の宣言が組み合わさることで、「`service` パッケージ以下には、 `controller` パッケージ以下のクラスに依存しているクラスが**存在しない**」ことをルールとして定義している
        - `noClasses()` なので、後ろのルールに該当するクラスが存在しないことを定義していることになる

### ルールをチェックする
```java:ArchUnitTest.java
...

public class ArchUnitTest {

    @Test
    void test() {
        ...        
        rule.check(javaClasses);
    }
}
```

- 作成した `ArchRule` の `check(JavaClasses)` メソッドに、インポートした `JavaClasses` を渡す
- これにより、 `JavaClasses` に含まれるクラスが `ArchRule` で定義されたルールを守っているかどうかを検証できる

# ３つのAPI
ArchUnit には、次の３つの API が用意されている

- Core API
- Lang API
- Library API

## Core API

```java:FooController
package example.controller;

import example.service.FooService;

public class FooController {
    private final FooService fooService;

    public FooController(FooService fooService) {
        this.fooService = fooService;
    }
    
    public String invoke() {
        return fooService.execute();
    }
}
```

```java:CoreAPIでクラスの情報を参照している例
package example;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import example.controller.FooController;

public class Main {

    public static void main(String[] args) {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example");

        JavaClass fooControllerClass = javaClasses.get(FooController.class);

        fooControllerClass.getFields().forEach(field -> {
            System.out.println("name=" + field.getName() + ", type=" + field.getRawType());
        });

        fooControllerClass.getMethods().forEach(method -> {
            System.out.println("name=" + method.getName() + ", return type=" + method.getRawReturnType());
        });
    }
}
```

```text:実行結果
name=fooService, type=JavaClass{name='example.service.FooService'}
name=invoke, return type=JavaClass{name='java.lang.String'}
```

- ArchUnit の最も基本となる API
- [JavaClass](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/JavaClass.html) や [JavaMethod](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/package-summary.html) のような、 Java プログラムの要素を表す型で構成されている
- リフレクション API と同じように、クラスのメタ情報を参照できる
- さらに、クラスの参照情報なども取得できるようになっている

```java:クラスの参照情報を取得する例
package example;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import example.service.FooService;

public class Main {

    public static void main(String[] args) {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example");

        JavaClass fooServiceClass = javaClasses.get(FooService.class);
        fooServiceClass.getDirectDependenciesToSelf().forEach(System.out::println);
    }
}
```

```text:実行結果(実際は３行で出力されるが、見やすいように少し整形している)
Dependency{
  originClass=JavaClass{name='example.controller.FooController'},
  targetClass=JavaClass{name='example.service.FooService'},
  lineNumber=13,
  description=Method <example.controller.FooController.invoke()> calls method <example.service.FooService.execute()> in (FooController.java:13)
}
Dependency{
  originClass=JavaClass{name='example.controller.FooController'},
  targetClass=JavaClass{name='example.service.FooService'},
  lineNumber=0,
  description=Field <example.controller.FooController.fooService> has type <example.service.FooService> in (FooController.java:0)
}
Dependency{
  originClass=JavaClass{name='example.controller.FooController'},
  targetClass=JavaClass{name='example.service.FooService'},
  lineNumber=0,
  description=Constructor <example.controller.FooController.<init>(example.service.FooService)> has parameter of type <example.service.FooService> in (FooController.java:0)
}
```

- `getDirectDependenciesToSelf()` により、 `FooService` を直接参照している場所を取得している
- このように、 Core API は解析対象となる Java プログラムの情報を参照するための、最も低レベルな API を提供している

### Core API は抽象度が低い
- 理屈上は、この Core API を使うことで Java プログラムの構造を解析し、意図したアーキテクチャ・ルールを守れているかをチェックできる
- しかし、そのためにはプログラムの情報を抽出して検証する処理を自力で実装する必要があり、かなり大変な作業になる
- Core API は最も基本的な API のため何でもできるが、「チェックしたいアーキテクチャ・ルール」を記述するには抽象度が低すぎる
- そこで、 Core API をラップしてより抽象度の高い記述をできるようにした Lang API が用意されている

### 主要クラスの型階層
![archunit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/9f094436-94fa-9547-8703-2fe6651a82d0.jpeg)

- Core API に含まれる Java コードの構造を表す主要なクラスは、上図のような関係になっている
- `JavaMember` はメソッド・コンストラクタ・フィールドをまとめたものを表し、  
  `JavaCodeUnit` はメソッドとコンストラクタをまとめたものを表している
- `JavaClass` は、 `JavaMember` とは独立した存在となっている
- その他のクラスも含めた完全な図は [ユーザーガイドの図](https://www.archunit.org/userguide/html/000_Index.html#_domain) を参照

## Lang API
```java
package example;

import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class Main {

    public static void main(String[] args) {
        ArchRule rule = noClasses().that().resideInAPackage("..service..")
                            .should().dependOnClassesThat().resideInAPackage("..controller..");

        System.out.println(rule.getDescription());
    }
}
```

```text:実行結果
no classes that reside in a package '..service..' should depend on classes that reside in a package '..controller..'
```

- Lang API は、前述の Core API よりも抽象度の高い記述でアーキテクチャのルールを定義できる API となっている
- `ArchRuleDefinition` のファクトリメソッドを起点として、流れるようなインタフェースでルールを記述できる


## Library API
```java
package example;

import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.library.Architectures.*;

public class Main {

    public static void main(String[] args) {
        ArchRule rule = layeredArchitecture()
                .layer("Controller").definedBy("..controller..")
                .layer("Service").definedBy("..service..")
                .layer("Persistence").definedBy("..persistence..")
                .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
                .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
                .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service");

        System.out.println(rule.getDescription());
    }
}
```

```text:実行結果
Layered architecture consisting of
layer 'Controller' ('..controller..')
layer 'Service' ('..service..')
layer 'Persistence' ('..persistence..')
where layer 'Controller' may not be accessed by any layer
where layer 'Service' may only be accessed by layers ['Controller']
where layer 'Persistence' may only be accessed by layers ['Service']
```

- Library API は、特定のアーキテクチャパターンに関するルールを、より簡潔に記述できるようにした API を提供している
- ここでは、レイヤードアーキテクチャのレイヤーを定義し、レイヤー間の依存関係のルールを宣言している
- 2020年11月現在、レイヤードアーキテクチャ以外にもオニオンアーキテクチャ用の API も用意されている

# クラスの読み込み
```text:プロジェクト構成
`-src/
  |-main/java/
  | `-example/archunit/
  |   |-Hoge.java
  |   |-foo/
  |   | `-Foo.java
  |   `-bar/
  |     `-Bar.java
  `-test/java/
    `-example/
      `-Main.java
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;

public class Main {

    public static void main(String[] args) {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit");
        javaClasses.forEach(System.out::println);
    }
}
```

```text:実行結果
JavaClass{name='example.archunit.bar.Bar'}
JavaClass{name='example.archunit.foo.Foo'}
JavaClass{name='example.archunit.Hoge'}
```

- ArchUnit を使うためには、まずは検証対象となるクラスを読み込む必要がある
- クラスの読み込みには、 [ClassFileImporter](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ClassFileImporter.html) を使用する
- [importPackages(String...)](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ClassFileImporter.html#importPackages(java.lang.String...)) メソッドを使用すると、引数で指定したパッケージ以下を再帰的に探索してクラスを読み込むことができる
- 読み込み結果は [JavaClasses](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/JavaClasses.html) という型で返される
- クラスは、実行時のクラスパスから読み込まれる
- したがって、テスト実行時などは `src/test/java` 以下のクラスも読み込み対象になるので注意が必要
    - テストクラスの読み込みを避けるには [ImportOption を利用する](#組み込みの-importoption)

```java:
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;

public class Main {

    public static void main(String[] args) {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example"); // 対象を example パッケージに変更
        javaClasses.forEach(System.out::println);
    }
}
```

```text:実行結果
JavaClass{name='example.archunit.foo.Foo'}
JavaClass{name='example.archunit.bar.Bar'}
JavaClass{name='example.Main'}
JavaClass{name='example.archunit.Hoge'}
```

- `src/test/java` フォルダ以下の `Main` クラスも読み込まれている


## パス指定で読み込む
```text:プロジェクト構成
|-build.gradle
|-build/
| |-classes/java/main/
| : `-example/archunit/
|     :
`-src/
```

- Gradle でコンパイルすると、 `src/main/java` 以下のソースのコンパイル結果は `build/classes/java/main` 以下に出力される
- この場所を指定して読み込むこともできる

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;

public class Main {

    public static void main(String[] args) {
        JavaClasses javaClasses = new ClassFileImporter().importPath("build/classes/java/main");
        javaClasses.forEach(System.out::println);
    }
}
```

```text:実行結果
JavaClass{name='example.archunit.bar.Bar'}
JavaClass{name='example.archunit.foo.Foo'}
JavaClass{name='example.archunit.Hoge'}
```

- [importPath(String)](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ClassFileImporter.html#importPath(java.lang.String)) メソッドを使うと、引数で指定したパスからクラスファイルを読み込む

## その他の読み込み方
上記以外にも、特定のクラスだけを読み込んだり、 `URL` 指定で読み込む方法なども提供されている。
詳細は、 [ClassFileImporter の Javadoc](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ClassFileImporter.html) を参照。

## 読み込み対象の場所を絞る
```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;

public class Main {

    public static void main(String[] args) {
        JavaClasses javaClasses = new ClassFileImporter()
                .withImportOption(location -> location.contains("/foo/") || location.contains("/bar/"))
                .importPackages("example.archunit");
        javaClasses.forEach(System.out::println);
    }
}
```

```text:実行結果
JavaClass{name='example.archunit.bar.Bar'}
JavaClass{name='example.archunit.foo.Foo'}
```

- [withImportOption(ImportOption)](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ClassFileImporter.html#withImportOption(com.tngtech.archunit.core.importer.ImportOption)) を使用すると、読み込み対象となる場所を任意に絞ることができる
    - `withImportOption()` は、 `ImportOption` を置き換えた新しい `ClassFileImporter` インスタンスを返す
    - 元の `ClassFileImport` インスタンスはそのままなので注意
- [ImportOption](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ImportOption.html) の [includes(Location)](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ImportOption.html) メソッドには、現在読み込もうとしている場所が [Location](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/Location.html) オブジェクトで渡されるので、この場所を対象とするかどうかを `boolean` で返すように実装する

### 組み込みの ImportOption
```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;

public class Main {

    public static void main(String[] args) {
        JavaClasses javaClasses = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .importPackages("example");
        javaClasses.forEach(System.out::println);
    }
}
```

- [ImportOption.Predefined](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ImportOption.Predefined.html) に、よく使いそうな `ImportOption` の定数が用意されている
- [DO_NOT_INCLUDE_ARCHIVES](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ImportOption.Predefined.html#DO_NOT_INCLUDE_ARCHIVES)
    - アーカイブを対象外にする
    - アーカイブと jar の違いは [Location.isArchive()](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/Location.html#isArchive()) で説明されている
    - JAR ファイルと、Java 9 で追加された JRT ファイルとかいうのを含んだものをアーカイブと呼んでいる
- [DO_NOT_INCLUDE_JARS](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ImportOption.Predefined.html#DO_NOT_INCLUDE_JARS)
    - JAR ファイルを対象外にする
- [DO_NOT_INCLUDE_TESTS](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ImportOption.Predefined.html#DO_NOT_INCLUDE_TESTS)
    - テストコードを対象外にする
    - 具体的には、 `Location` の場所が次のいずれかの正規表現にマッチすると対象外になる
        - `.*/target/test-classes/.*`
        - `.*/build/classes/([^/]+/)?test/.*`
        - `.*/out/test/classes/.*`

## 依存するクラスの自動的な読み込み
```text:パッケージ構成
`-example/archunit/
  |-fizz/
  | |-Hoge.java
  | `-Fuga.java
  `-buzz/
    |-Foo.java
    `-Bar.java
```

```java:Hoge.java
package example.archunit.fizz;

@Deprecated
public class Hoge {}
```

```java:Fuga.java
package example.archunit.fizz;

public class Fuga {}
```

```java:Foo.java
package example.archunit.buzz;

import example.archunit.fizz.Hoge;

public class Foo {
    Hoge hoge = new Hoge();
}
```

```java:Bar.java
package example.archunit.buzz;

import example.archunit.fizz.Fuga;

public class Bar {
    Fuga fuga = new Fuga();
}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit.buzz");
        javaClasses.forEach(System.out::println);

        final ArchRule rule = noClasses()
                .should().accessClassesThat().areAnnotatedWith(Deprecated.class);

        rule.check(javaClasses);
    }
}
```

- `example.archunit.buzz` パッケージを読み込んで、 `@Deprecated` で注釈されたクラスにアクセスしているクラスが存在しないことをチェックしている

```text:実行結果
JavaClass{name='example.archunit.buzz.Bar'}
JavaClass{name='example.archunit.buzz.Foo'}

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes should access classes that are annotated with @Deprecated' was violated (1 times):
Constructor <example.archunit.buzz.Foo.<init>()> calls constructor <example.archunit.fizz.Hoge.<init>()> in (Foo.java:6)
```

- `ClassFileImporter` で読み込んだクラスは `Foo`, `Bar` の２つだけになっている（`Hoge` クラスは読み込まれていない）
- しかし、 `Hoge` クラスが `@Deprecated` で注釈されており、制約に違反していることは判定できている
- このように、読み込んだクラスが依存しているクラスは、たとえインポート対象に入っていなくても自動的に読み込まれるようになっている
- この仕組みのおかげで、インポート対象の指定は検証対象だけを意識した設定で済むようになっている
- しかし、もしこの自動的な読み込みがパフォーマンスに影響を与えるような場合は、次のようにして自動的な読み込みをオフにすることもできる
    - 自動的にインポートされるクラスが大量にあるが、それらが検証で利用されていないようなケース

### 自動的な読み込みをオフにする
```properties:archunit.properties
resolveMissingDependenciesFromClassPath=false
```

- テスト実行時のクラスパス直下に `archunit.properties` というファイルを配置する
- ここに、 `resolveMissingDependenciesFromClassPath=false` と設定する
- すると、依存先の自動的な読み込み機能がオフになる
- 上と同じテストを、このプロパティファイルを配置した状態で実行すると、結果は下のようになる

```text:自動読み込みをオフにした場合の実行結果
JavaClass{name='example.archunit.buzz.Bar'}
JavaClass{name='example.archunit.buzz.Foo'}

Process finished with exit code 0
```

- テストは正常終了した
- 自動読み込みがオフになっている場合、インポート先に指定していないクラスは、自動的にスタブに置き換えられる
    - この場合、 `Hoge` クラスがスタブに置き換えられて解析が行われている
- スタブは要はハリボテなので、オリジナルのクラスが持つ特徴（`@Deprecated` で注釈されていること）は反映されていない
- したがって、テストは通ってしまっている
- 自動読み込みの機能をオフにする場合は、依存先のクラスもインポート対象に含まれるように注意して指定する必要がある

# Lang API の文法
Lang API は、基本的に以下の文法で記述できるようになっている。

```
<対象> that <対象を絞り込む条件> should <検証する制約>
```

例えば、 Hello World で書いた Lang API の記述は、次のように読み取ることができる。

```java
// <対象>: 制約を満たす対象が存在しないことを定義
noClasses()
  .that()
  // <対象を絞り込む条件>: service 以下のパッケージに絞り込み
  .resideInAPackage("..service..")
  .should()
  // <検証する制約>: 「controller パッケージに依存している」という制約を定義
  .dependOnClassesThat().resideInAPackage("..controller..");
```

つまり、「`<対象> that <対象を絞り込む条件>`」で検証対象を特定し、「`should <検証する制約>`」で検証内容を記述するという形になっている。

## 条件の追加
```java
package example;

import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class Main {

    public static void main(String[] args) {
        ArchRule rule =
            noClasses()
                .that()
                    .resideInAPackage("..service..")
                    .or().resideInAPackage("..persistence..")
                .should()
                    .accessClassesThat().resideInAPackage("..controller..")
                    .orShould().accessClassesThat().resideInAPackage("..ui..");

        System.out.println(rule.getDescription());
    }
}
```

```text:実行結果
no classes that reside in a package '..service..' or reside in a package '..persistence..' should access classes that reside in a package '..controller..' or should access classes that reside in a package '..ui..'
```

- `<対象を絞り込む条件>` や `<検証する制約>` の部分は、 `or` や `and` で条件を追加できるようになっている

# 対象の選択
- Lang API で検証対象を選択するには [ArchRuleDefinition](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/ArchRuleDefinition.html) に定義された `static` なファクトリメソッドを使用する
- まずは、分かりやすいものだけを表でまとめる

|メソッド|説明|
|---|---|
|`classes()`|全型（クラス・インタフェース）を対象にする。<br>メンバークラス、ローカルクラス、匿名クラスも対象。|
|`theClass(Class)`, `theClass(String)`|特定の型だけを対象にする。文字列の方は完全名を渡す。|
|`constructors()`|全コンストラクタを対象にする。|
|`fields()`|全フィールドを対象にする。|
|`methods()`|全メソッドを対象にする。|
|`members()`|全メンバー（コンストラクタ・フィールド・メソッド）を対象にする。<br>ここで言っているメンバーとは、[主要クラスの型階層](#主要クラスの型階層)で出てきた `JavaMember` を指しているので、メンバークラスは関係ない。|
|`codeUnits()`|全コードユニット（コンストラクタ・メソッド）を対象にする。|

- さらに、これらを否定するメソッドも用意されている
    - `noClasses()`
    - `noClass(Class)`, `noClass(String)`
    - `noConstructors()`
    - `noFields()`
    - `noMethods()`
    - `noMembers()`
    - `noCodeUnits()`
- これらは、 `should()` 以後の条件に一致する対象が存在しないことを定義したいときに使用する

# 対象の絞り込み条件
- `that()` の後ろで指定する、対象を絞り込むためのメソッドについて整理する
- `that()` の戻り値の型は、最初に対象を選択したメソッド(`classes()` とか `methods()` とか)によって変化する

|対象の選択|`that()`が返す型|
|---|---|
|`classes().that()`|[ClassesThat](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/ClassesThat.html)|
|`members().that()`|[MembersThat](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/MembersThat.html)|
|`codeUnits().that()`|[CodeUnitsThat](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/CodeUnitsThat.html)|
|`constructors().that()`|[CodeUnitsThat](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/CodeUnitsThat.html) (※`ConstrutcorsThat` は無い)|
|`methods().that()`|[MethodsThat](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/MethodsThat.html)|
|`fields().that()`|[FieldsThat](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/FieldsThat.html)|

- `MembersThat` や `CodeUnitThat` は [主要クラスの型階層](#主要クラスの型階層) と同じ継承関係になっているので、親で定義された絞り込みのメソッドは子のクラスでも使用できる

## 対象を絞り込むメソッドの一覧
**注意事項**

- `areNotAnnotatedWith()` のような否定形のメソッドは省略している（基本的にどのメソッドにも否定形のメソッドが用意されている）
- `areAnnotatedWith(String)`, `areAnnotatedWith(Class)` のようにオーバーロードされているメソッドは、１種類だけ（基本的に `Class` 型のものだけ）記載している

### ClassesThat
|メソッド|説明|
|---|---|
|`areAnnotatedWith(Class)`|指定されたアノテーションで注釈されている型に絞る|
|`areAnonymousClasses()`|匿名クラスに絞る|
|`areAssignableFrom(Class)`|指定された型のインスタンスを代入可能な型に絞る<br>(`対象型の変数 = 引数の型のインスタンス;`)|
|`areAssignableTo(Class)`|指定された型に代入可能なクラスに絞る<br>(`引数の型の変数 = 対象型のインスタンス;`)|
|`areEnums()`|enumに絞る|
|`areInnerClasses()`|内部クラスに絞る|
|`areInterfaces()`|インタフェースに絞る|
|`areLocalClasses()`|ローカルクラスに絞る|
|`areMemberClasses()`|メンバークラスに絞る|
|`areMetaAnnotatedWith(Class)`|指定されたメタアノテーションで注釈されたアノテーションで注釈されている型に絞る。|
|`areNestedClasses()`|ネストクラスに絞る|
|`arePackagePrivate()`|パッケージプライベートな型に絞る|
|`arePrivate()`|private な型に絞る|
|`areProtected()`|protected な型に絞る|
|`arePublic()`|public な型に絞る|
|`areTopLevelClasses()`|トップレベルクラスに絞る|
|`belongToAnyOf(Class...)`|指定された型と、その型に属する全てのネストクラスを対象にする|
|`haveFullyQualifiedName(String)`|完全修飾名が指定された文字列と一致する型に絞る|
|`haveModifier(JavaModifier)`|指定された修飾子を持つ型に絞る|
|`haveNameMatching(String)`|完全修飾名が指定された正規表現にマッチする型に絞る|
|`haveSimpleName(String)`|単純名が指定された文字列と一致する型に絞る|
|`haveSimpleNameContaining(String)`|単純名に指定された文字列を含む型に絞る|
|`haveSimpleNameEndingWith(String)`|単純名が指定された文字列で終わる型に絞る|
|`haveSimpleNameStartingWith(String)`|単純名が指定された文字列で始まる型に絞る|
|`implement(Class)`|指定されたインタフェースを実装したクラスに絞る|
|`resideInAnyPackage(String...)`|指定されたいずれかのパッケージに含まれる型に絞る|
|`resideInAPackage(String)`|指定されたパッケージに含まれる型に絞る|
|`resideOutsideOfPackage(String)`|指定されたパッケージに含まれない型に絞る|
|`resideOutsideOfPackages(String...)`|指定されたいずれのパッケージにも含まれない型に絞る|

### MembersThat
|メソッド|説明|
|---|---|
|`areAnnotatedWith(Class)`|指定されたアノテーションで注釈されているメンバーに絞る|
|`areDeclaredIn(Class)`|指定された型内で宣言されているメンバーに絞る（親の型で宣言されているものは含まない）|
|`areDeclaredInClassesThat()`|この後で定義する条件を満たすクラスで宣言されているメンバーに絞る|
|`areMetaAnnotatedWith(Class)`|指定されたメタアノテーションで注釈されたアノテーションで注釈されているメンバーに絞る|
|`arePackagePrivate()`|パッケージプライベートなメンバーに絞る|
|`arePrivate()`|private なメンバーに絞る|
|`areProtected()`|protected なメンバーに絞る|
|`arePublic()`|public なメンバーに絞る|
|`haveFullName(String)`|完全名が指定された文字列と一致するメンバーに絞る。|
|`haveFullNameMatching(String)`|完全名が指定された正規表現にマッチするメンバーに絞る|
|`haveModifier(JavaModifier)`|指定された修飾子を持つメンバーに絞る|
|`haveName(String)`|名前が指定された文字列と一致するメンバーに絞る|
|`haveNameMatching(String)`|名前が指定された正規表現にマッチするメンバーに絞る|

### CodeUnitsThat
|メソッド|説明|
|---|---|
|`declareThrowableOfType(Class)`|throws句に指定された型の例外を含むコードユニットに絞る|
|`haveRawParameterTypes(Class...)`|仮引数の型の並びが指定されたものと一致するコードユニットに絞る|
|`haveRawReturnType(Class)`|戻り値の型が指定されたものと一致するコードユニットに絞る|

### MethodsThat
|メソッド|説明|
|---|---|
|`areFinal()`|final なメソッドに絞る|
|`areStatic()`|static なメソッドに絞る|

### FieldsThat
|メソッド|説明|
|---|---|
|`areFinal()`|final なフィールドに絞る|
|`areStatic()`|static なフィールドに絞る|
|`haveRawType(Class)`|型が指定されたものと一致するフィールドに絞る|

## 対象を絞り込むメソッドの詳細
実際の動きを見てみないと理解しづらいメソッドに絞って説明。

### 匿名クラス・内部クラス・ローカルクラス・メンバークラス・ネストクラス・トップレベルクラスの違い
```java:TopLevelClass
package example.archunit;

public class TopLevelClass {
    class MemberClass {}
    static class StaticMemberClass {}
    interface MemberInterface {}
    
    void method() {
        class LocalClass {}
        Object anonymousClass = new Object() {};
    }
}
```

```java:TopLevelInterface
package example.archunit;

public class TopLevelInterface {}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit");

        report("areAnonymousClasses()", javaClasses, classes().that().areAnonymousClasses().should().beAnnotatedWith(Deprecated.class));
        report("areInnerClasses()", javaClasses, classes().that().areInnerClasses().should().beAnnotatedWith(Deprecated.class));
        report("areLocalClasses()", javaClasses, classes().that().areLocalClasses().should().beAnnotatedWith(Deprecated.class));
        report("areMemberClasses()", javaClasses, classes().that().areMemberClasses().should().beAnnotatedWith(Deprecated.class));
        report("areNestedClasses()", javaClasses, classes().that().areNestedClasses().should().beAnnotatedWith(Deprecated.class));
        report("areTopLevelClasses()", javaClasses, classes().that().areTopLevelClasses().should().beAnnotatedWith(Deprecated.class));
    }
    
    void report(String title, JavaClasses javaClasses, ArchRule rule) {
        System.out.println("[" + title + "]");
        System.out.println(rule.evaluate(javaClasses).getFailureReport() + "\n");
    }
}
```

```text:実行結果
[areAnonymousClasses()]
Architecture Violation [Priority: MEDIUM] - Rule 'classes that are anonymous classes should be annotated with @Deprecated' was violated (1 times):
Class <example.archunit.TopLevelClass$1> is not annotated with @Deprecated in (TopLevelClass.java:0)

[areInnerClasses()]
Architecture Violation [Priority: MEDIUM] - Rule 'classes that are inner classes should be annotated with @Deprecated' was violated (3 times):
Class <example.archunit.TopLevelClass$1> is not annotated with @Deprecated in (TopLevelClass.java:0)
Class <example.archunit.TopLevelClass$1LocalClass> is not annotated with @Deprecated in (TopLevelClass.java:0)
Class <example.archunit.TopLevelClass$MemberClass> is not annotated with @Deprecated in (TopLevelClass.java:0)

[areLocalClasses()]
Architecture Violation [Priority: MEDIUM] - Rule 'classes that are local classes should be annotated with @Deprecated' was violated (1 times):
Class <example.archunit.TopLevelClass$1LocalClass> is not annotated with @Deprecated in (TopLevelClass.java:0)

[areMemberClasses()]
Architecture Violation [Priority: MEDIUM] - Rule 'classes that are member classes should be annotated with @Deprecated' was violated (3 times):
Class <example.archunit.TopLevelClass$MemberClass> is not annotated with @Deprecated in (TopLevelClass.java:0)
Class <example.archunit.TopLevelClass$MemberInterface> is not annotated with @Deprecated in (TopLevelClass.java:0)
Class <example.archunit.TopLevelClass$StaticMemberClass> is not annotated with @Deprecated in (TopLevelClass.java:0)

[areNestedClasses()]
Architecture Violation [Priority: MEDIUM] - Rule 'classes that are nested classes should be annotated with @Deprecated' was violated (5 times):
Class <example.archunit.TopLevelClass$1> is not annotated with @Deprecated in (TopLevelClass.java:0)
Class <example.archunit.TopLevelClass$1LocalClass> is not annotated with @Deprecated in (TopLevelClass.java:0)
Class <example.archunit.TopLevelClass$MemberClass> is not annotated with @Deprecated in (TopLevelClass.java:0)
Class <example.archunit.TopLevelClass$MemberInterface> is not annotated with @Deprecated in (TopLevelClass.java:0)
Class <example.archunit.TopLevelClass$StaticMemberClass> is not annotated with @Deprecated in (TopLevelClass.java:0)

[areTopLevelClasses()]
Architecture Violation [Priority: MEDIUM] - Rule 'classes that are top level classes should be annotated with @Deprecated' was violated (2 times):
Class <example.archunit.TopLevelClass> is not annotated with @Deprecated in (TopLevelClass.java:0)
Class <example.archunit.TopLevelInterface> is not annotated with @Deprecated in (TopLevelInterface.java:0)
```

整理すると、以下のような関係になっている。

![archunit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/fadb141f-bc01-1d2d-60b3-239fd97e1a8f.jpeg)

- 以下２つがポイントだと思う
    - `areInnerClasses()` は `static` なクラスは対象外
    - `areNestedClasses()` は、トップレベルクラス内で宣言された全てのクラスが対象になる

### メタアノテーションとは
```text:パッケージ構成
`-example/archunit/
  |-MetaAnnotation.java
  |-FooAnnotation.java
  |-BarAnnotation.java
  |-Foo.java
  `-Bar.java
```

```java:MetaAnnotation.java
package example.archunit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MetaAnnotation {}
```

```java:FooAnnotation.java
package example.archunit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@MetaAnnotation // ★@MetaAnnotation で注釈されている
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FooAnnotation {}
```

```java:BarAnnotation.java
package example.archunit;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// ★@MetaAnnotation では注釈されていない
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface BarAnnotation {}
```

```java:Foo.java
package example.archunit;

@FooAnnotation
public class Foo {}
```

```java:Bar.java
package example.archunit;

@BarAnnotation
public class Bar {}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import example.archunit.MetaAnnotation;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit");

        ArchRule rule = classes().that().areMetaAnnotatedWith(MetaAnnotation.class)
                .should().beAnnotatedWith(Deprecated.class);
        
        rule.check(javaClasses);
    }
}
```

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes that are meta-annotated with @MetaAnnotation should be annotated with @Deprecated' was violated (1 times):
Class <example.archunit.Foo> is not annotated with @Deprecated in (Foo.java:0)
```

- メタアノテーションとは、「アノテーションを注釈するアノテーション」のこと
- 上の例では `@MetaAnnotation` がメタアノテーションで、 `@FooAnnotation` を注釈している
- `areMetaAnnotatedWith(Class)` は、引数で指定されたメタアノテーション(`@MetaAnnotaion`)で注釈されているアノテーション(`@FooAnnotation`)で注釈されている型(`Foo`)に絞っている
- `Bar` を注釈している `BarAnnotation` はメタアノテーションで注釈されていないので、対象にはならない
- Spring Framework の `@Component` とかがメタアノテーションになる

### 完全修飾名
```java:Hoge.java
package example.archunit;

import java.util.List;

public class Hoge {
    int primitive;
    String object;
    int[] primitiveArray;
    String[] objectArray;
    int[][] nestedPrimitiveArray;
    String[][] nestedObjectArray;
    List<String> genericClass;
    MemberClass memberClass;
    StaticMemberClass staticMemberClass;
    
    class MemberClass {}
    static class StaticMemberClass {}
}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import example.archunit.Hoge;

public class Main {

    public static void main(String[] args) {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit");

        final JavaClass hogeClass = javaClasses.get(Hoge.class);
        hogeClass.getFields().forEach(field -> {
            System.out.println(field.getRawType().getFullName());
        });
    }
}
```

```text:実行結果
int
java.lang.String
[I
[[Ljava.lang.String;
example.archunit.Hoge$StaticMemberClass
[Ljava.lang.String;
[[I
example.archunit.Hoge$MemberClass
java.util.List
```

- `haveFullyQualifiedName(String)` とかで使う完全修飾名は、 Java 言語仕様の完全修飾名とは一致していない
    - Java 言語仕様での定義は[完全限定名と正規名とバイナリ名](https://qiita.com/opengl-8080/items/74b1644c6d122c29e0cb)を参照
- おそらく、 [Class.getName()](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Class.html#getName()) が返す値と一致している

#### コードユニットの完全名
```java:Hoge.java
package example.archunit;

import java.util.List;

public class Hoge {
    Hoge(int i) {}
    public void method1() {}
    protected String method2(int i, String s, int[] ints, List<String> list) {return null;}

    public static class MemberClass {
        void method() {}
    }
}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import example.archunit.Hoge;

public class Main {

    public static void main(String[] args) {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit");

        final JavaClass hogeClass = javaClasses.get(Hoge.class);
        hogeClass.getCodeUnits().forEach(method -> System.out.println(method.getFullName()));

        final JavaClass memberClass = javaClasses.get(Hoge.MemberClass.class);
        memberClass.getCodeUnits().forEach(method -> System.out.println(method.getFullName()));
    }
}
```

```text:実行結果
example.archunit.Hoge.method1()
example.archunit.Hoge.method2(int, java.lang.String, [I, java.util.List)
example.archunit.Hoge.<init>(int)
example.archunit.Hoge$MemberClass.method()
example.archunit.Hoge$MemberClass.<init>()
```

- コードユニットの完全名は、 `型の完全修飾名.メソッド名(仮引数の型の完全修飾名, ...)` となる
    - コンストラクタの場合は、 `メソッド名` が `<init>` となる

### パッケージのパターン指定
- `resideInAPackage(String)` などのパッケージをマッチングさせるメソッドでは、 AspectJ 風の特殊な記法でパッケージのパターンを指定できる
- 具体的には、次の２つのパターンが用意されている
    - `*`：ドット(`.`)以外の、１文字以上の文字列
    - `..`：０以上の任意の数のパッケージ
- 下図は、パッケージ構成に対してパターンがどのようにマッチするかをまとめている
    - 丸印のところが、パターンに対してパッケージ名がマッチするケースになる

![archunit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/081d05a1-235e-3117-b65f-bbe81aa0f827.jpeg)



# 制約の定義
- `should()` の後ろで指定する、制約（ルール）を定義するメソッドについて整理する
- `that()` と同じで、選択した対象によって `should()` の戻り値の型は次のように変化する

|対象の選択|`should()`が返す型|
|---|---|
|`classes().should()`|[ClassesShould](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/ClassesShould.html)|
|`members().should()`|[MembersShould](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/MembersShould.html)|
|`codeUnits().should()`|[CodeUnitsShould](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/CodeUnitsShould.html)|
|`constructors().should()`|[CodeUnitsShould](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/CodeUnitsShould.html) （※`ConstructorsShould` は無い）|
|`methods().should()`|[MethodsShould](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/MethodsShould.html)|
|`fields().should()`|[FieldsShould](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/elements/FieldsShould.html)|

## 制約を定義するメソッドの一覧
**注意事項**

- `notBe(Class)` のような否定形のメソッドは省略している
- `be(Class)`, `be(String)` のようにオーバーロードされているメソッドは、１種類だけ（基本的に `Class` 型のものだけ）記載している

### ClassesShould
|メソッド|説明|
|---|---|
|`accessClassesThat()`|対象の型に、この後で定義する条件を満たす型にアクセスしている箇所が１つ以上あることを制約にする。<br>否定形での利用が前提。|
|`accessField(Class, String)`|対象の型からアクセスしているフィールドのうち、最低１つは指定されたフィールドであることを制約にする。<br>否定形での利用が前提。|
|`be(Class)`|対象の型が、指定されたクラスと一致することを制約にする。|
|`beAnnotatedWith(Class)`|対象の型が、指定されたアノテーションで注釈されていることを制約にする。|
|`beAnonymousClasses()`|対象の型が匿名クラスであることを制約にする。|
|`beAssignableFrom(Class)`|対象の型の変数に、指定された型のインスタンスを代入できることを制約にする。(`対象型の変数 = 指定された型のインスタンス;`)|
|`beAssignableTo(Class)`|対象の型のインスタンスを、指定された型の変数に代入できることを制約にする。(`指定された型の変数 = 対象型のインスタンス;`)|
|`beEnums()`|対象の型が enum であることを制約にする。|
|`beInnerClasses()`|対象の型が内部クラスであることを制約にする。|
|`beInterfaces()`|対象の型がインタフェースであることを制約にする。|
|`beLocalClasses()`|対象の型がローカルクラスであることを制約にする。|
|`beMemberClasses()`|対象の型がメンバークラスであることを制約にする。|
|`beMetaAnnotatedWith(Class)`|対象の型が、指定されたメタアノテーションで注釈されたアノテーションで注釈されていることを制約にする。|
|`beNestedClasses()`|対象の型がネストされた型であることを制約にする。|
|`bePackagePrivate()`|対象の型がパッケージプライベートであることを制約にする。|
|`bePrivate()`|対象の型が private であることを制約にする。|
|`beProtected()`|対象の型が protected であることを制約にする。|
|`bePublic()`|対象の型が public であることを制約にする。|
|`beTopLevelClasses()`|対象の型がトップレベルであることを制約にする。|
|`callConstructor(Class, Class...)`|対象の型で実行しているコンストラクタのうち、最低１つは指定されたコンストラクタと一致することを制約にする。<br>否定形での利用が前提。|
|`callMethod(Class, String, Class...)`|対象の型で実行しているメソッドのうち、最低１つは指定されたメソッドと一致することを制約にする。<br>否定形での利用が前提。|
|`dependOnClassesThat()`|対象の型が、この後で定義する条件を満たす型に依存することを制約にする。<br>否定形での利用が前提。|
|`getField(Class, String)`|対象の型からアクセスしているフィールドのうち、最低１つは指定したフィールドの読み取りであることを制約にする。<br>否定形での利用が前提。|
|`haveFullyQualifiedName(String)`|対象の型の完全修飾名が、指定された文字列と一致することを制約にする。|
|`haveModifier(JavaModifier)`|対象の型が、指定された修飾子を持つことを制約にする。|
|`haveNameMatching(String)`|対象の型の完全修飾名が、指定された正規表現にマッチすることを制約にする。|
|`haveOnlyFinalFields()`|対象の型が持つフィールドが全て final であることを制約にする。|
|`haveOnlyPrivateConstructors()`|対象の型が持つコンストラクタが全て private であることを制約にする。|
|`haveSimpleName(String)`|対象の型の単純名が、指定された文字列と一致することを制約にする。|
|`haveSimpleNameContaining(String)`|対象の型の単純名が、指定された文字列を含むことを制約にする。|
|`haveSimpleNameEndingWith(String)`|対象の型の単純名が、指定された文字列で終わることを制約にする。|
|`haveSimpleNameStartingWith(String)`|対象の型の単純名が、指定された文字列で始まることを制約にする。|
|`implement(Class)`|対象のクラスが、指定されたインタフェースを実装していることを制約にする。|
|`onlyAccessClassesThat()`|対象の型が、この後で定義する条件を満たすクラスのインスタンスにだけアクセスしていることを制約にする。|
|`onlyAccessFieldsThat(DescribedPredicate)`|対象の型が、指定した条件を満たすフィールドにだけアクセスしていることを制約にする。|
|`onlyAccessMembersThat(DescribedPredicate)`|対象の型が、指定した条件を満たすメンバーにだけアクセスしていることを制約にする。|
|`onlyBeAccessed()`|対象の型が、このあとに定義する条件を満たすクラスからのみアクセスされていることを制約にする。|
|`onlyCallCodeUnitsThat(DescribedPredicate)`|対象の型が、指定した条件を満たすコードユニットだけを呼び出していることを制約にする。|
|`onlyCallConstructorsThat(DescribedPredicate)`|対象の型が、指定した条件を満たすコンストラクタだけを呼び出していることを制約にする。|
|`onlyCallMethodsThat(DescribedPredicate)`|対象の型が、指定した条件を満たすメソッドだけを呼び出していることを制約にする。|
|`onlyDependOnClassesThat()`|対象の型が、この後に定義する条件を満たすクラスにだけ依存していることを制約にする。|
|`onlyHaveDependentClassesThat()`|対処の型が、この後に定義する条件を満たすクラスからのみ依存されていることを制約にする。|
|`resideInAnyPackage(String...)`|対象の型が、指定したいずれかのパッケージに含まれることを制約にする。|
|`resideInAPackage(String)`|対象の型が、指定されたパッケージに含まれることを制約にする。|
|`resideOutsideOfPackage(String)`|対象の型が、指定されたパッケージに含まれないこと制約にする。|
|`resideOutsideOfPackages(String)`|対象の型が、指定されたいずれのパッケージにも含まれないことを制約にする。|
|`setField(Class, String)`|対象の型からアクセスしているフィールドのうち、最低１つは指定したフィールドの書き込みであることを制約にする。<br>否定形での利用が前提。|

※`DescribedPredicate` を引数に受け取るメソッドは、後述する [事前定義された述語API](#事前定義された述語-api) を使うと比較的簡単に `DescribedPredicate` を構築できる。


### MembersShould
|メソッド|説明|
|---|---|
|`beAnnotatedWith(Class)`|対象のメンバーが、指定されたアノテーションで注釈されていることを制約にする。|
|`beDeclaredIn(Class)`|対象のメンバーが、指定された型の中で宣言されていることを制約にする。|
|`beDeclaredInClassesThat()`|対象のメンバーが、この後に定義する条件を満たす型の中で宣言されていることを制約にする。|
|`beMetaAnnotatedWith(Class)`|対象のメンバーが、指定されたメタアノテーションで注釈されたアノテーションで注釈されていることを制約にする。|
|`bePackagePrivate()`|対象のメンバーが、パッケージプライベートであることを制約にする。|
|`bePrivate()`|対象のメンバーが、 private であることを制約にする。|
|`beProtected()`|対象のメンバーが、 protected であることを制約にする。|
|`bePublic()`|対象のメンバーが、 public であることを制約にする。|
|`haveFullName(String)`|対象のメンバーの完全修飾名が、指定された文字列と一致することを制約にする。|
|`haveFullNameMatching(String)`|対象のメンバーの完全修飾名が、指定された正規表現にマッチすることを制約にする。|
|`haveModifier(JavaModifier)`|対象のメンバーが、指定された修飾子を持つことを制約にする。|
|`haveName(String)`|対象のメンバーの名前が、指定された文字列と一致することを制約にする。|
|`haveNameMatching(String)`|対象のメンバーの名前が、指定された正規表現にマッチすることを制約にする。|


### CodeUnitsShould
|メソッド|説明|
|---|---|
|`declareThrowableOfType(Class)`|対象のコードユニットの throws 句に、指定された例外の型が含まれていることを制約にする。|
|`haveRawParameterTypes(Class...)`|対象のコードユニットの仮引数が、指定された型の並びと一致することを制約にする。|
|`haveRawReturnType(Class)`|対象のコードユニットの戻り値の型が、指定された型と一致することを制約にする。|


### MethodsShould
|メソッド|説明|
|---|---|
|`beFinal()`|対象のメソッドが final であることを制約にする。|
|`beStatic()`|対象のメソッドが static であることを制約にする。|

### FieldsShould
|メソッド|説明|
|---|---|
|`beFinal()`|対象のフィールドが final であることを制約にする。|
|`beStatic()`|対象のフィールドが static であることを制約にする。|
|`haveRawType(Class)`|対象のフィールドの型が指定された型と一致することを制約にする。|

## 制約を定義するメソッドの詳細
### 否定形での利用を前提としたメソッド
- 以下のメソッドは、おそらく否定形で使用することが前提となっている
    - `accessClassesThat()`
    - `accessField(Class, String)`
    - `getField(Class, String)`
    - `setField(Class, String)`
    - `callConstructor(Class, Class...)`
    - `callMethod(Class, String, Class...)`
    - `dependOnClassesThat()`
- これらは、いずれも「条件に一致する処理が最低１つあること」を制約にする
- そのままだと、いまいち使い道が分からない
    - 制約の内容に対して、出力されるエラーメッセージが関係しておらず、一見すると意味がわからないエラーメッセージになる
- 以下は、 `accessClassesThat()` をそのまま利用した場合の例

```text:パッケージ構成
`-example/archunit/
  |-foo/
  | `-Foo.java
  |-bar/
  | `-Bar.java 
  `-target/
    |-NoAccess.java
    |-DeclareFooField.java
    `-CallFooConstructor.java
```

```java:Foo.java
package example.archunit.foo;

public class Foo {}
```

```java:Bar.java
package example.archunit.bar;

public class Bar {}
```

```java:NoAccess.java
package example.archunit.target;

public class NoAccess {}
```

```java:DeclareFooField.java
package example.archunit.target;

import example.archunit.foo.Foo;

public class DeclareFooField {
    Foo foo;
}
```

```java:CallFooConstructor.java
package example.archunit.target;

import example.archunit.bar.Bar;
import example.archunit.foo.Foo;

public class CallFooConstructor {
    Foo foo = new Foo();
    Bar bar = new Bar();
}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit.target");

        ArchRule rule = classes().should().accessClassesThat().resideInAPackage("example.archunit.foo");

        rule.check(javaClasses);
    }
}
```

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes should access classes that reside in a package 'example.archunit.foo'' was violated (2 times):
Constructor <example.archunit.target.DeclareFooField.<init>()> calls constructor <java.lang.Object.<init>()> in (DeclareFooField.java:5)
Constructor <example.archunit.target.NoAccess.<init>()> calls constructor <java.lang.Object.<init>()> in (NoAccess.java:3)
```

- `accessClassesThat()` は、対象の型が、この後ろで定義した条件を満たす型に１回以上アクセスしていることを制約として定義する
- 上記例では、 `example.archunit.target` 配下のクラスは、１回以上 `example.archunit.foo` 配下のクラスにアクセスしていることが制約として定義されている
- `NoAccess` は、全くアクセスしていないのでエラーになっている
- `DeclareFooField` は、一見すると `example.archunit.foo.Foo` を使っているので問題ないように見える
    - しかし、 ArchUnit における「アクセス」は、フィールドを参照したりメソッドを実行していることを表している
    - したがって、フィールドを定義しているだけの `DeclareFooField` は「アクセスしていない」ことになり、エラーとなっている
- `CallFooConstructor` は、コンストラクタを呼び出して「アクセスしている」ので、エラーにはなっていない
    - 関係ないクラス `Bar` にもアクセスしているが、これはこの制約には関係しない

#### 否定形で使用する
- 上記例を見ると、制約内容に対してエラーメッセージの内容が全然マッチしていなくて、何が原因でエラーになっているのかわからない
- 実際のところ、この `accessClassesThat()` や上で挙げた `getField()` などのメソッドは、否定形で使用されることが想定されている（たぶん）

```java:否定形で使用している例
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit.target");

        ArchRule rule = noClasses().should().accessClassesThat().resideInAPackage("example.archunit.foo");

        rule.check(javaClasses);
    }
}
```

- `noClasses()` になって、 `example.archunit.target` 配下には `example.archunit.foo` 配下のクラスにアクセスしているクラスは存在しないことが制約となっている

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes should access classes that reside in a package 'example.archunit.foo'' was violated (1 times):
Constructor <example.archunit.target.CallFooConstructor.<init>()> calls constructor <example.archunit.foo.Foo.<init>()> in (CallFooConstructor.java:6)
```

- `Foo` のコンストラクタを実行している `CallFooConstructor` がエラーになった
- エラーメッセージも、制約内容と整合の取れた内容になっていて、問題の原因が理解できるようになっている

## only 系のメソッド
- 名前に `only` が付く以下のメソッドは、名前の通り「その対象にだけを利用している・されている」ことを制約にできる
    - `onlyAccessClassesThat()`
    - `onlyAccessFieldsThat(DescribedPredicate)`
    - `onlyAccessMembersThat(DescribedPredicate)`
    - `onlyCallCodeUnitsThat(DescribedPredicate)`
    - `onlyCallConstructorsThat(DescribedPredicate)`
    - `onlyCallMethodsThat(DescribedPredicate)`
    - `onlyBeAccessed()`
    - `onlyDependOnClassesThat()`
    - `onlyHaveDependentClassesThat()`
- ただし、この `only` はちょっと癖があるので使い方に注意が必要
- 以下は、 `onlyAccessClassesThat()` の利用例

```text:パッケージ構成
`-example/archunit/
  |-foo/
  | `-Foo.java
  |-bar/
  | `-Bar.java
  `-target/
    |-NoAccess.java
    |-AccessFoo.java
    `-AccessBar.java
```

```java:Foo.java
package example.archunit.foo;

public class Foo {
    public String value;
}
```

```java:Bar.java
package example.archunit.bar;

public class Bar {}
```

```java:NoAccess.java
package example.archunit.target;

public class NoAccess {}
```

```java:AccessFoo.java
package example.archunit.target;

import example.archunit.foo.Foo;

public class AccessFoo {
    String value = new Foo().value;
}
```

```java:AccessBar.java
package example.archunit.target;

import example.archunit.bar.Bar;

public class AccessBar {
    Bar bar = new Bar();
}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit.target");
        
        ArchRule rule = classes().should().onlyAccessClassesThat().resideInAPackage("example.archunit.foo");

        rule.check(javaClasses);
    }
}
```

- `example.archunit.target` 配下のクラスは、 `example.archunit.foo` パッケージ配下のクラスにだけアクセスしていることを制約にしているつもりのテストコード
- `AccessBar` は `example.archunit.bar.Bar` にアクセスしているので、テストは落ちる気がする
- 一方で、 `NoAccess` はどこにもアクセスしていないし `AccessFoo` は `Foo` にしかアクセスしていないので、この２つはテストが通りそうな気がする
- しかし、実際の結果は次のようになる

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes should only access classes that reside in a package 'example.archunit.foo'' was violated (6 times):
Constructor <example.archunit.target.AccessBar.<init>()> calls constructor <example.archunit.bar.Bar.<init>()> in (AccessBar.java:6)
Constructor <example.archunit.target.AccessBar.<init>()> calls constructor <java.lang.Object.<init>()> in (AccessBar.java:5)
Constructor <example.archunit.target.AccessBar.<init>()> sets field <example.archunit.target.AccessBar.bar> in (AccessBar.java:6)
Constructor <example.archunit.target.AccessFoo.<init>()> calls constructor <java.lang.Object.<init>()> in (AccessFoo.java:5)
Constructor <example.archunit.target.AccessFoo.<init>()> sets field <example.archunit.target.AccessFoo.value> in (AccessFoo.java:6)
Constructor <example.archunit.target.NoAccess.<init>()> calls constructor <java.lang.Object.<init>()> in (NoAccess.java:3)
```

- `AccessBar` がエラーになるのは良いとして、 `NoAccess`, `AccessFoo` もエラーになってしまっている
- `NoAccess` は、コンストラクタで `Object` クラスのコンストラクタを呼んでいるとしてエラーになっている
- `AccessFoo` は、同じくコンストラクタのエラーに加えて、自分自身のフィールド `value` にアクセスしていることでエラーとなっている
- このように、 `only` のついた制約はかなり厳しい条件となる
- したがって、上記テストを意図通りに動かしたい場合は、アクセスを許可するパッケージを以下のように緩和する必要がある

```java:許可パッケージを緩和した例
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPackages("example.archunit.target");
        
        ArchRule rule = classes().should().onlyAccessClassesThat()
                .resideInAnyPackage("example.archunit.foo", "example.archunit.target", "java..");

        rule.check(javaClasses);
    }
}
```

- `resideInAnyPackage(String...)` を使い、複数のパッケージを指定できるようにしている
- 対象のパッケージに、対象自身を含むパッケージと `java` パッケージ配下を追加することで、条件を緩和している

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes should only access classes that reside in any package ['example.archunit.foo', 'example.archunit.target', 'java..']' was violated (1 times):
Constructor <example.archunit.target.AccessBar.<init>()> calls constructor <example.archunit.bar.Bar.<init>()> in (AccessBar.java:6)
```

- これで、当初期待したとおり `AccessBar` だけがエラーになった


# 対象・条件・制約のカスタマイズ
[対象の選択](#対象の選択) の表を見ると、「全てのパッケージを対象にする」メソッドは用意されていないことが分かる。
パッケージ以外にも、分析したい視点によって対象は様々なケースが考えられる。

`ArchRuleDefinition` では、これら全てのケースに対応したメソッドを用意しておくことはできない。
その代わりに、任意の対象を抽出するための API が用意されている。

```java
package 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.domain.JavaPackage;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.AbstractClassesTransformer;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ClassesTransformer;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import org.junit.jupiter.api.Test;

import java.util.stream.Collectors;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses javaClasses = new ClassFileImporter().importPath("build/classes/java/main");

        // 全パッケージを対象とする
        ClassesTransformer<JavaPackage> packages = new AbstractClassesTransformer<>("packages") {
            @Override
            public Iterable<JavaPackage> doTransform(JavaClasses classes) {
                return classes.stream().map(JavaClass::getPackage).collect(Collectors.toSet());
            }
        };

        // クラスを含むパッケージのみを条件にする
        DescribedPredicate<JavaPackage> haveClasses = new DescribedPredicate<>("contains classes") {
            @Override
            public boolean apply(JavaPackage javaPackage) {
                return !javaPackage.getClasses().isEmpty();
            }
        };

        // package-info.java を持つことを制約にする
        ArchCondition<JavaPackage> havePackageInfo = new ArchCondition<>("have package-info.java") {
            @Override
            public void check(JavaPackage javaPackage, ConditionEvents events) {
                if (!javaPackage.tryGetPackageInfo().isPresent()) {
                    events.add(SimpleConditionEvent.violated(javaPackage, javaPackage.getName() + " package does not have package-info.java."));
                }
            }
        };

        ArchRule rule = all(packages).that(haveClasses).should(havePackageInfo);

        rule.check(javaClasses);
    }
}
```

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'packages that contains classes should have package-info.java' was violated (4 times):
example.controller package does not have package-info.java.
example.sandbox package does not have package-info.java.
example.service package does not have package-info.java.
example.target package does not have package-info.java.
```

## 対象のカスタマイズ
```java
        // 全パッケージを対象とする
        ClassesTransformer<JavaPackage> packages = new AbstractClassesTransformer<>("packages") {
            @Override
            public Iterable<JavaPackage> doTransform(JavaClasses classes) {
                return classes.stream().map(JavaClass::getPackage).collect(Collectors.toSet());
            }
        };
```

- [all(ClassesTransformer)](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/syntax/ArchRuleDefinition.html#all(com.tngtech.archunit.lang.ClassesTransformer)) を使うことで、対象を絞り込む処理をカスタマイズできる
    - [ClassesTransformer](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/ClassesTransformer.html) を実装したクラスを `all()` メソッドに渡す
    - [AbstractClassesTransformer](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/AbstractClassesTransformer.html) を継承して作れば、 `doTransform(JavaClasses)` だけを実装すればよくなる
    - `doTransform()` には読み込まれた `JavaClasses` が渡されるので、対象にしたいオブジェクトに変換して `return` する
- `all()` メソッドを使用した場合、その後の `that()` と `should()` も、同じようにカスタマイズしたクラスを渡す必要がある

## 条件のカスタマイズ
```java
        // クラスを含むパッケージのみを条件にする
        DescribedPredicate<JavaPackage> haveClasses = new DescribedPredicate<>("contains classes") {
            @Override
            public boolean apply(JavaPackage javaPackage) {
                return !javaPackage.getClasses().isEmpty();
            }
        };
```

- `that(DescribedPredicate)` には、対象を絞り込む条件を実装した [DescribedPredicate](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/base/DescribedPredicate.html) インスタンスを渡す
    - コンストラクタ引数には、 `ArchRule` の `getDescription()` で返される説明に使用される文言(`that` の後ろ)を設定する
    - `packages that contains classes should have package-info.java` の `contains classes` の部分
- `apply(T)` メソッドは、受け取った対象が条件に一致する場合に `true` を返すよう実装する

### 事前定義された述語 API
- `all()` を使った場合は、常に `DescribedPredicate` を実装したクラスを用意しなければならないのかというと、そうでもない
- 述語を記述するための事前定義された API が用意されており、それを使えば `all()` を使わない場合と同じような記述ができる

```java
package example;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;

import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;

public class Main {

    public static void main(String[] args) {
        DescribedPredicate<JavaClass> resideInTheFooPackage = resideInAPackage("..foo..");
        ...
    }
}
```

- `JavaClass` のメンバークラスである [Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/JavaClass.Predicates.html) に、 `JavaClass` の条件を絞り込むための述語を記述するための API が用意されている
- このように述語を記述するための API を定義したクラスは、以下のものが用意されている
    - [AccessTarget.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/AccessTarget.Predicates.html)
    - [CanBeAnnotated.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/CanBeAnnotated.Predicates.html)
    - [Dependency.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/Dependency.Predicates.html)
    - [HasModifiers.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/HasModifiers.Predicates.html)
    - [HasName.AndFullName.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/HasName.AndFullName.Predicates.html)
    - [HasName.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/HasName.Predicates.html)
    - [HasOwner.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/HasOwner.Predicates.html)
    - [HasOwner.Predicates.With](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/HasOwner.Predicates.With.html)
    - [HasParameterTypes.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/HasParameterTypes.Predicates.html)
    - [HasReturnType.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/HasReturnType.Predicates.html)
    - [HasThrowsClause.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/HasThrowsClause.Predicates.html)
    - [HasType.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/properties/HasType.Predicates.html)
    - [JavaAccess.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/JavaAccess.Predicates.html)
    - [JavaCall.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/JavaCall.Predicates.html)
    - [JavaClass.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/JavaClass.Predicates.html)
    - [JavaCodeUnit.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/JavaCodeUnit.Predicates.html)
    - [JavaFieldAccess.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/JavaFieldAccess.Predicates.html)
    - [JavaMember.Predicates](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/domain/JavaMember.Predicates.html)

### 条件の連結
```java
package example;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;

import java.io.Serializable;

import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;

public class Main {

    public static void main(String[] args) {
        DescribedPredicate<JavaClass> isHogeSerializable = type(Serializable.class).and(name("Hoge"));
        ...
    }
}
```

- [DescribedPredicate](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/base/DescribedPredicate.html) には、条件を連結させるために `and()` や `or()` などの様々なメソッドが用意されている
- これをつなげることで、より複雑な条件を組み立てられる

### 連結順序の制約
- 上の例では、 `type(Serializable.class).and(name("Hoge"))` のように先に `Serializable` 型であることを定義して、その後で名前が `Hoge` である条件を追加している
- これを、もし逆順で組み立てようとするとコンパイルエラーになる

```java:逆順で組み立てようとした場合
DescribedPredicate<HasName> isHogeSerializable = name("Hoge").and(type(Serializable.class));
// java: 不適合な型: com.tngtech.archunit.base.DescribedPredicate<com.tngtech.archunit.core.domain.JavaClass>を
//   com.tngtech.archunit.base.DescribedPredicate<? super com.tngtech.archunit.core.domain.properties.HasName>に変換できません:
```

- 順序を逆にしても意味は変わらないはずだが、 Java の言語仕様の制約上、このコンパイルエラーは回避できない
    - `DescribedPredicate<T>` の `and()` メソッドの引数は、 `DescribedPredicate<? super T>` という下限付き境界ワイルドカード型で定義されている
    - したがって、
        - `JavaClass.Predicates.type()` の `and()` は、引数の型が `DescribedPredicate<? super JavaClass>` となり、  
          `HasName.Predicates.name()` の `and()` は、引数の型が `DescribedPredicate<? super HasName>` となる
    - `JavaClass implements HasName` の関係があるので、
        - 引数 `DescribedPredicate<? super JavaClass>` に `DescribedPredicate<HasName>` は渡せるが、  
          引数 `DescribedPredicate<? super HasName>` に `DescribedPredicate<JavaClass>` は渡せない
- これを回避する方法は、以下の２つのいずれかになる
    - コンパイルが通る順序で書く
    - `forSubType()` を使って型の情報を変更する
- `forSubType()` を使った場合は、次のような実装になる

```java:forSubType()を使った例
package example;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;

import java.io.Serializable;

import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;

public class Main {

    public static void main(String[] args) {
        // forSubType() を使って、無理やり型を DescribedPredicate<JavaClass> に変更する
        DescribedPredicate<JavaClass> nameIsHoge = name("Hoge").forSubType();
        DescribedPredicate<JavaClass> isHogeSerializable = nameIsHoge.and(type(Serializable.class));
        ...
    }
}
```

## 制約のカスタマイズ
```java
        // package-info.java を持つことを制約にする
        ArchCondition<JavaPackage> havePackageInfo = new ArchCondition<>("have package-info.java") {
            @Override
            public void check(JavaPackage javaPackage, ConditionEvents events) {
                if (!javaPackage.tryGetPackageInfo().isPresent()) {
                    events.add(SimpleConditionEvent.violated(javaPackage, javaPackage.getName() + " package does not have package-info.java."));
                }
            }
        };
```

- `should(ArchCondition)` には、検証する制約の処理を実装した [ArchCondition](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/ArchCondition.html) インスタンスを渡す
    - コンストラクタ引数には、 `ArchRule` の `getDescription()` で返される説明に使用される文言(`should` の後ろ)を設定する
    - `packages that contains classes should have package-info.java` の `have package-info.java` の部分
- `check(T, ConditionEvents)` メソッドで検証処理を実装する
    - 検証エラーの場合は、第二引数で受け取った [ConditionEvents](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/ConditionEvents.html) に違反の情報を追加する

### 事前定義された API
- こちらも、条件の絞り込みのための述語 API と同じように、事前定義された API が用意されている
- ただし、述語の API はいくつかの `Predicates` クラスに分かれて定義されていたのに対して、制約を定義するための API は [ArchConditions](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/lang/conditions/ArchConditions.html) クラスに全てまとめられている

```java:ArchConditionsに定義されたメソッドでArchConditionを構築する
package example;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.lang.ArchCondition;

import static com.tngtech.archunit.lang.conditions.ArchConditions.accessClassesThatResideIn;

public class Main {

    public static void main(String[] args) {
        ArchCondition<JavaClass> resideInFooPackage = accessClassesThatResideIn("..foo..");
        ...
    }
}
```


# 参照元の取得
- ArchUnit の Core API は、標準ライブラリのリフレクション API と似た機能を提供している
- しかし、リフレクションにはない独自の重要な機能として、参照元を取得できるという特徴がある

```text:パッケージ構成
`-example/archunit/
  |-foo/
  | |-Foo.java
  | `-Bar.java
  |-fizz/
  | |-Fizz.java
  | `-Buzz.java
  `-target/
    `-Hoge.java
```

```java:Foo.java
package example.archunit.foo;

import example.archunit.target.Hoge;

public class Foo {
    void foo() {
        new Hoge().method();
    }
}
```

```java:Bar.java
package example.archunit.foo;

import example.archunit.target.Hoge;

public class Bar {
    String value = new Hoge().field;
}
```

```java:Fizz.java
package example.archunit.fizz;

public class Fizz {
    public void fizz() {}
}
```

```java:Buzz.java
package example.archunit.fizz;

public class Buzz {
    public String buzz;
}
```

```java:Hoge.java
package example.archunit.target;

import example.archunit.fizz.Buzz;
import example.archunit.fizz.Fizz;

public class Hoge {
    public String field;
    public void method() {
        new Fizz().fizz();
        String value = new Buzz().buzz;
    }
}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import example.archunit.target.Hoge;

public class Main {

    public static void main(String[] args) {
        final JavaClasses classes = new ClassFileImporter().importPackages("example.archunit");

        final JavaClass hogeClass = classes.get(Hoge.class);

        hogeClass.getFieldAccessesToSelf().forEach(System.out::println);
        hogeClass.getFieldAccessesFromSelf().forEach(System.out::println);
        hogeClass.getMethodCallsToSelf().forEach(System.out::println);
        hogeClass.getMethodCallsFromSelf().forEach(System.out::println);
    }
}
```

```text:実行結果
JavaFieldAccess{origin=JavaConstructor{example.archunit.foo.Bar.<init>()}, target=target{example.archunit.target.Hoge.field}, lineNumber=6, accessType=GET}
JavaFieldAccess{origin=JavaMethod{example.archunit.target.Hoge.method()}, target=target{example.archunit.fizz.Buzz.buzz}, lineNumber=10, accessType=GET}
JavaMethodCall{origin=JavaMethod{example.archunit.foo.Foo.foo()}, target=target{example.archunit.target.Hoge.method()}, lineNumber=7}
JavaMethodCall{origin=JavaMethod{example.archunit.target.Hoge.method()}, target=target{example.archunit.fizz.Fizz.fizz()}, lineNumber=9}
```

- `getFieldAccessesToSelf()` や `getMethodCallsFromSelf()` などのメソッド、その対象に（から）アクセスしているモノを取得できる
- `～ToSelf` は、その対象にアクセスしているモノを抽出し、  
  `～FromSelf` は、その対象からアクセスしているモノを抽出する
- ここでは、クラスに（から）アクセスしているモノを全て抽出するメソッドを使用しているが、 `hogeClass.getField("field").getAccessesToSelf()` や `hogeClass.getMethod("method").getAccessesFromSelf()` のように特定の要素に（から）アクセスしているモノを取得することもできる

## 参照を表すクラス
![archunit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/3cc13ce9-e72b-c5f3-5b8f-1fef7bf0056c.jpeg)

- 参照を表すクラスは、上図のような構成になっている
- 参照には向きがあり、 `origin` が参照元、 `target` が参照先を表している

## 参照先を表すクラス
![archunit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/cc859c2f-ea5a-da1c-ebd6-fda55c1778d2.jpeg)

- 参照先を表すクラスは、次の３つが存在する
    - `FieldAccessTarget`
    - `MethodCallTarget`
    - `ConstructorCallTarget`
- これらは、参照を表すクラス(`JavaFieldAccess` など)と１対１の関係になっている
- 一方で、具体的な参照先を表すクラス(`JavaField` など)との関係は、 `0` や `*` があったりするのが分かる
    - アクセスしているはずなのに具体的な参照先が `0` であったり、メソッドの呼び出し先が複数あるのは、普通に考えると不自然に思える

### アクセス先が０になるケース
```text:パッケージ構成
`-src/
  |-main/java/
  | `-example/archunit/
  |   |-foo/
  |   | `-Parent.java
  |   `-target/
  |     |-Child.java
  |     `-Hoge.java
  `-test/resources/
    `-archunit.properties
```

```properties:archunit.properties
resolveMissingDependenciesFromClassPath=false
```

- [自動的な読み込みをオフにしている](#自動的な読み込みをオフにする)

```java:Parent.java
package example.archunit.foo;

public class Parent {
    public String field;
    public void method() {}
}
```

```java:Child.java
package example.archunit.target;

import example.archunit.foo.Parent;

public class Child extends Parent {}
```

```java:Hoge.java
package example.archunit.target;

public class Hoge {
    void hoge() {
        final Child child = new Child();
        child.field = "test";
        child.method();
    }
}
```

```java
package example;

import com.tngtech.archunit.core.domain.AccessTarget;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaField;
import com.tngtech.archunit.core.domain.JavaFieldAccess;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.domain.JavaMethodCall;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import example.archunit.target.Hoge;

import java.util.Set;

public class Main {

    public static void main(String[] args) {
        final JavaClasses classes = new ClassFileImporter().importPackages("example.archunit.target");

        final JavaClass hogeClass = classes.get(Hoge.class);

        for (JavaFieldAccess javaFieldAccess : hogeClass.getFieldAccessesFromSelf()) {
            final AccessTarget.FieldAccessTarget target = javaFieldAccess.getTarget();
            final JavaField javaField = target.resolveField().orNull();
            System.out.println("owner=" + target.getOwner() + ", javaField=" + javaField);
        }

        for (JavaMethodCall javaMethodCall : hogeClass.getMethodCallsFromSelf()) {
            final AccessTarget.MethodCallTarget target = javaMethodCall.getTarget();
            final Set<JavaMethod> javaMethods = target.resolve();
            System.out.println("owner=" + target.getOwner() + ", javaMethods=" + javaMethods);
        }
    }
}
```

- `example.archunit.target` をインポートして、 `Hoge` からアクセスしている先を抽出している

```text:実行結果
owner=JavaClass{name='example.archunit.target.Child'}, javaField=null
owner=JavaClass{name='example.archunit.target.Child'}, javaMethods=[]
```

- 具体的な参照先の情報は空になっている
- このように、参照先のフィールドやメソッドが親クラスに定義されていて、その親クラスがインポート対象に含まれていない場合に、具体的な参照先が空になる
- ちなみに、自動的な読み込みをオンにして実行したら、下のように情報がちゃんと取れる

```text:自動的な読み込みをオンにした場合の実行結果
owner=JavaClass{name='example.archunit.target.Child'}, javaField=JavaField{example.archunit.foo.Parent.field}
owner=JavaClass{name='example.archunit.target.Child'}, javaMethods=[JavaMethod{example.archunit.foo.Parent.method()}]
```

### メソッドのアクセス先が複数になるケース
```text:パッケージ構成
`-example/archunit/
  |-foo/
  | |-FooInterface.java
  | |-BarInterface.java
  | |-AbstractClass.java
  | `-ConcreteClass.java
  `-target/
    `-Hoge.java
```

```java:FooInterface.java
package example.archunit.foo;

public interface FooInterface {
    void method();
}
```

```java:BarInterface.java
package example.archunit.foo;

public interface BarInterface {
    void method();
}
```

- 全く同じシグネチャのメソッドが、 `FooInterface` と `BarInterface` の両方に定義されている

```java:AbstractClass.java
package example.archunit.foo;

public abstract class AbstractClass implements FooInterface, BarInterface {}
```

- `FooInterface` と `BarInterface` の両方を実装した抽象クラスがあるが、インタフェースで定義されている抽象メソッドを実装していない

```java:ConcreteClass.java
package example.archunit.foo;

public class ConcreteClass extends AbstractClass {
    @Override
    public void method() {}
}
```

- 具象クラスでは、当然抽象メソッドを実装している

```java:Hoge.java
package example.archunit.target;

import example.archunit.foo.AbstractClass;
import example.archunit.foo.ConcreteClass;

public class Hoge {
    void hoge() {
        AbstractClass cc = new ConcreteClass();
        cc.method();
    }
}
```

- 抽象メソッド(`method()`) を、抽象クラス(`AbstractClass`)をレシーバにして実行している

```java
package example;

import com.tngtech.archunit.core.domain.AccessTarget;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.domain.JavaMethodCall;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import example.archunit.target.Hoge;

import java.util.Set;

public class Main {

    public static void main(String[] args) {
        final JavaClasses classes = new ClassFileImporter().importPackages("example.archunit.target");

        final JavaClass hogeClass = classes.get(Hoge.class);

        for (JavaMethodCall javaMethodCall : hogeClass.getMethodCallsFromSelf()) {
            final AccessTarget.MethodCallTarget target = javaMethodCall.getTarget();
            final Set<JavaMethod> javaMethods = target.resolve();
            System.out.println("owner=" + target.getOwner() + ", javaMethods=" + javaMethods);
        }
    }
}
```

- フィールドのときと同じように、呼び出し先のメソッドの情報を出力している

```text:実行結果
owner=JavaClass{name='example.archunit.foo.AbstractClass'}, javaMethods=[JavaMethod{example.archunit.foo.BarInterface.method()}, JavaMethod{example.archunit.foo.FooInterface.method()}]
```

- 解決されたメソッドが、インタフェースで定義された２つのメソッドになっている
- 要するに、下図のような関係になっていると、 ArchUnit は呼び出し先の具体的なメソッドを一意に特定できなくなり、候補となるメソッドが全て取得されるようになっている

![archunit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/3c923949-f0ac-6ee2-7c3a-2043ac8826fc.jpeg)

- ちなみに、 `AbstractClass` で `method()` を実装していた場合は以下のようになる

```text:AbstractClassでmethod()を実装している場合の実行結果
owner=JavaClass{name='example.archunit.foo.AbstractClass'}, javaMethods=[JavaMethod{example.archunit.foo.AbstractClass.method()}]
```

- ところで、 `AbstractClass` が抽象メソッドを実装していない状態（`ConcreteClass` が実装している最初の状態）で、メソッド呼び出しのレシーバを `ConcreteClass` にすると、次のような結果になる

```java:レシーバをConcreteClassにした場合実装
package example.archunit.target;

import example.archunit.foo.ConcreteClass;

public class Hoge {
    void hoge() {
        ConcreteClass cc = new ConcreteClass();
        cc.method();
    }
}
```

```text:レシーバがConcreteClassのときの実行結果
owner=JavaClass{name='example.archunit.foo.ConcreteClass'}, javaMethods=[JavaMethod{example.archunit.foo.ConcreteClass.method()}, JavaMethod{example.archunit.foo.BarInterface.method()}, JavaMethod{example.archunit.foo.FooInterface.method()}]
```

- `ConcreteClass.method()` だけになるのかなと思ったら、インタフェースも抽出された
- この動きの理由はよくわからない

# エラーの説明を変更する
```text:パッケージ構成
`-example/archunit/
  |-foo/
  | `-Foo.java
  `-bar/
    `-Bar.java
```

```java:Foo.java
package example.archunit.foo;

import example.archunit.bar.Bar;

public class Foo {
    Bar bar;
}
```

```java:Bar.java
package example.archunit.bar;

public class Bar {}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        final JavaClasses classes = new ClassFileImporter().importPackages("example.archunit");

        // foo パッケージは bar パッケージに依存してはいけない
        ArchRule rule = noClasses().that().resideInAPackage("..foo..")
                .should().dependOnClassesThat().resideInAPackage("..bar..");

        rule.check(classes);
    }
}
```

```text:実行結果（エラーの説明のみ）
Rule 'no classes that reside in a package '..foo..' should depend on classes that reside in a package '..bar..'' was violated
```

- デフォルトでは、 `ArchRule` の内容に従って自動的に組み立てられた説明が出力される
- この説明はかなり分かりやすい形になっているので、たいていの場合はデフォルトの出力で問題はない
- しかし、次のようなケースではデフォルトの説明だけでは十分ではないことがありえる
    - ルールの意図を説明したい場合（何故そのようなルールを設けているのか、設計の意図を伝えたい）
    - `or` や `and` で条件を組み合わせた結果、デフォルトで出力される説明が分かりにくくなった場合
- エラー時の説明は、以下の方法で調整できる

## ルールの意図を追加する
```java
package example;

...

public class ArchUnitTest {

    @Test
    void test() {
        final JavaClasses classes = new ClassFileImporter().importPackages("example.archunit");

        ArchRule rule = noClasses().that().resideInAPackage("..foo..")
                .should().dependOnClassesThat().resideInAPackage("..bar..")
                .because("俺が気に入らないから");

        rule.check(classes);
    }
}
```

```text:実行結果
Rule 'no classes that reside in a package '..foo..' should depend on classes that reside in a package '..bar..', because 俺が気に入らないから' was violated
```

- `ArchRule` の定義の中で `because(String)` メソッドを使うことで、説明の後ろに意図を説明する because 文が追加される

## 説明を完全に置き換える
```java
package example;

...

public class ArchUnitTest {

    @Test
    void test() {
        final JavaClasses classes = new ClassFileImporter().importPackages("example.archunit");

        ArchRule rule = noClasses().that().resideInAPackage("..foo..")
                .should().dependOnClassesThat().resideInAPackage("..bar..")
                .as("foo が bar に依存するとかマジないわー");

        rule.check(classes);
    }
}
```

```text:実行結果
Rule 'foo が bar に依存するとかマジないわー' was violated
```

- `as(String)` メソッドを使用すると、エラー時の説明を完全に置き換えることが可能
- `because(String)` との併用も可能

# 違反を無視する
- 既存プロジェクトに ArchUnit を適用していて段階的にチェックを入れていきたいような場合、一部のコードは制約違反があってもテストが失敗しないようにしたくなる
- 一部のコードだけテストの対象外にしたい場合、例えば `that()` の後で対象となるコードを絞り込むといった方法が考えられる
- また、これ以外にも ArchUnit には特定の制約違反があっても無視するようにできる手段が用意されている

```text:パッケージ構成
`-src/
  |-main/java/
  | `-example/archunit/
  |   |-foo/
  |   | `-Foo.java
  |   `-target/
  |     |-Hoge.java
  |     `-Fuga.java
  `-test/resources/
    `-archunit_ignore_patterns.txt
```

- クラスパスのルートに `archunit_ignore_patterns.txt` というテキストファイルが来るようにしている

```text:archunit_ignore_patterns.txt
# Hoge は特別扱い
.*Hoge.*
```

```java:Foo.java
package example.archunit.foo;

public class Foo {}
```

```java:Hoge.java
package example.archunit.target;

import example.archunit.foo.Foo;

public class Hoge {
    Foo foo;
}
```

```java:Fuga.java
package example.archunit.target;

import example.archunit.foo.Foo;

public class Fuga {
    Foo foo;
}
```

- いずれも、 `Foo` クラスに依存している

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses classes = new ClassFileImporter().importPackages("example.archunit");

        ArchRule rule = noClasses().that().resideInAPackage("..target..")
                .should().dependOnClassesThat().resideInAPackage("..foo..");

        rule.check(classes);
    }
}
```

- `target` パッケージ以下のクラスは `foo` パッケージ以下のクラスに依存しないことをテスト
- 通常なら、 `Hoge`, `Fuga` 両方のクラスがエラーになる

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package '..target..' should depend on classes that reside in a package '..foo..'' was violated (1 times):
Field <example.archunit.target.Fuga.foo> has type <example.archunit.foo.Foo> in (Fuga.java:0)
```

- 実際にエラーになったのは、 `Fuga` クラスのみになった
- このように、クラスパスルートに `archunit_ignore_patterns.txt` というテキストファイルを配置することで、特定の制約違反を無視できるようになる
- `archunit_ignore_patterns.txt` には、無視したい**制約違反のメッセージとマッチさせる**正規表現を記述する
    - クラス名とかではなく、違反のメッセージにマッチさせる点に注意
    - 上記エラーメッセージでいうと、 `Field <example.archunit.target.Fuga.foo> has type <example.archunit.foo.Foo> in (Fuga.java:0)` の部分にマッチさせる正規表現となる
- `#` で始まる行はコメント扱いになる

# Library API
## レイヤードアーキテクチャのテスト
![archunit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/9bfb7a6b-13e7-dadf-471c-c3bbcbb3a71e.jpeg)

仮に上図のように定義された依存関係をテストする場合の例。

```text:パケージ構成
`-example/archunit/
  |-domain/
  | |-RepositoryInterface.java
  | `-DomainObject.java
  |-infrastructure/
  | `-RepositoryImpleClass.java
  |-service/
  | `-ServiceClass.java
  `-presentation/
    `-PresentationClass.java
```

```java:DomainObject.java
package example.archunit.domain;

public class DomainClass {
    public String method() {
        return "domain";
    }
}
```

```java:RepositoryInterface.java
package example.archunit.domain;

public interface RepositoryInterface {
    DomainClass find();
}
```

```java:RepositoryImplClass.java
package example.archunit.infrastructure;

import example.archunit.domain.DomainClass;
import example.archunit.domain.RepositoryInterface;

public class RepositoryImplClass implements RepositoryInterface {
    @Override
    public DomainClass find() {
        return new DomainClass();
    }
}
```

```java:ServiceClass.java
package example.archunit.service;

import example.archunit.domain.DomainClass;
import example.archunit.domain.RepositoryInterface;
import example.archunit.presentation.PresentationClass;

public class ServiceClass {
    // ★Service が Presentation に依存してしまっている
    private final PresentationClass presentationClass;
    private final RepositoryInterface repositry;

    public ServiceClass(PresentationClass presentationClass, RepositoryInterface repositry) {
        this.presentationClass = presentationClass;
        this.repositry = repositry;
    }

    public String method() {
        DomainClass domainObject = repositry.find();
        return domainObject.method();
    }
}
```

```java:PresentationClass.java
package example.archunit.presentation;

import example.archunit.service.ServiceClass;

public class PresentationClass {
    private final ServiceClass service;

    public PresentationClass(ServiceClass service) {
        this.service = service;
    }
    
    public String method() {
        return service.method();
    }
}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.library.Architectures.*;

public class ArchUnitTest {

    @Test
    void test() {
        JavaClasses classes = new ClassFileImporter().importPackages("example.archunit");

        ArchRule rule = layeredArchitecture()
                .layer("Domain").definedBy("example.archunit.domain..")
                .layer("Infrastructure").definedBy("example.archunit.infrastructure..")
                .layer("Service").definedBy("example.archunit.service..")
                .layer("Presentation").definedBy("example.archunit.presentation..")
                .whereLayer("Presentation").mayNotBeAccessedByAnyLayer()
                .whereLayer("Service").mayOnlyBeAccessedByLayers("Presentation")
                .whereLayer("Infrastructure").mayOnlyBeAccessedByLayers("Service", "Presentation")
                .whereLayer("Domain").mayOnlyBeAccessedByLayers("Presentation", "Service", "Infrastructure");

        rule.check(classes);
    }
}
```

- [Architectures.layeredArchitecture()](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/library/Architectures.html#layeredArchitecture()) を使って、レイヤー間の依存関係を定義している
- `layer(String).definedBy(String)` で、レイヤーの名前と含まれるパッケージを定義している
- `whereLayer(String)` と `mayNotBeAccessedByAnyLayer()` と `mayOnlyBeAccessedByLaysers(String...)` で、レイヤー間の依存関係を定義している
    - `mayNotBeAccessedByAnyLayer()` は、そこからも依存されないことを定義している
    - `mayOnlyBeAccessedByLayers(String...)` は、 `whereLayer(String)` で指定したレイヤーにアクセスできるレイヤー（複数可）を定義している

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Layered architecture consisting of
layer 'Domain' ('example.archunit.domain..')
layer 'Infrastructure' ('example.archunit.infrastructure..')
layer 'Service' ('example.archunit.service..')
layer 'Presentation' ('example.archunit.presentation..')
where layer 'Presentation' may not be accessed by any layer
where layer 'Service' may only be accessed by layers ['Presentation']
where layer 'Infrastructure' may only be accessed by layers ['Service', 'Presentation']
where layer 'Domain' may only be accessed by layers ['Presentation', 'Service', 'Infrastructure']' was violated (2 times):
Constructor <example.archunit.service.ServiceClass.<init>(example.archunit.presentation.PresentationClass, example.archunit.domain.RepositoryInterface)> has parameter of type <example.archunit.presentation.PresentationClass> in (ServiceClass.java:0)
Field <example.archunit.service.ServiceClass.presentationClass> has type <example.archunit.presentation.PresentationClass> in (ServiceClass.java:0)
```

- `Service` レイヤーが `Presentation` レイヤーに依存してしまっているので、テストはエラーとなった

## 循環参照のチェック
```text:パッケージ構成
`-example/archunit/
  |-one/
  | |-One.java
  | `-Foo.java
  |-two/
  | `-Two.java
  |-three/
  | `-Three.java
  |
  `-sub/
    |-hoge/
    | `-Hoge.java
    |-fuga/
    | `-Fuga.java
    `-piyo/
      `-Piyo.java
```

```java:One.java
package example.archunit.one;

import example.archunit.two.Two;

public class One {
    private Two two;

    public One(Two two) {
        this.two = two;
    }
}
```

```java:Two.java
package example.archunit.two;

import example.archunit.three.Three;

public class Two {
    private Three three;

    public Two(Three three) {
        this.three = three;
    }
}
```

```java:Three.java
package example.archunit.three;

import example.archunit.one.Foo;

public class Three {
    private Foo foo;

    public Three(Foo foo) {
        this.foo = foo;
    }
}
```

- `one.One` -> `two.Two` -> `three.Three` -> `one.Foo` という形で、パッケージの循環参照が発生している
- 実装は省略するが、 `sub` 以下のクラスは `sub.hoge.Hoge` -> `sub.fuga.Fuga` -> `sub.piyo.Piyo` -> `sub.hoge.Hoge` -> ... のような循環参照になっている

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;

import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.*;

public class ArchUnitTest {

    @Test
    void test1() {
        JavaClasses classes = new ClassFileImporter().importPackages("example.archunit");

        ArchRule rule = slices().matching("example.archunit.(*)").should().beFreeOfCycles();

        rule.check(classes);
    }

    @Test
    void test2() {
        JavaClasses classes = new ClassFileImporter().importPackages("example.archunit");

        ArchRule rule = slices().matching("example.archunit.(**)").should().beFreeOfCycles();

        rule.check(classes);
    }
}
```

- `test1()` は、 `matching()` の引数が `"example.archunit.(*)"`、  
  `test2()` は、 `matching()` の引数が `"example.archunit.(**)"` になっている

```text:test1の実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'slices matching 'example.archunit.(*)' should be free of cycles' was violated (1 times):
Cycle detected: Slice one -> Slice two -> Slice three -> Slice one
...
```

```text:test2の実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'slices matching 'example.archunit.(**)' should be free of cycles' was violated (2 times):
Cycle detected: Slice one -> Slice two -> Slice three -> Slice one
...
Cycle detected: Slice sub.fuga -> Slice sub.piyo -> Slice sub.hoge -> Slice sub.fuga
...
```

- パッケージの循環参照をチェックするには、まず [SlicesRuleDefinition.slices()](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/library/dependencies/SlicesRuleDefinition.html#slices()) と [matching(String)](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/library/dependencies/SlicesRuleDefinition.Creator.html#matching(java.lang.String)) で対象のパッケージを絞り込む
    - `matching(String)` の引数には、対象のパッケージを特定するためのパターンを指定する
    - 基本は [resideInAPackage(String) で指定していたパターン](#パッケージのパターン指定) と同じ
    - ただし、対象のパッケージを限定するための丸括弧 `()` が必要
        - 丸括弧で囲われた部分が異なるパッケージ間での依存の有無がチェックされる
        - `a.(*)..` のような指定の場合
            - `a.b` と `a.c` の間で循環参照があればエラーになる
            - `a.b.d` と `a.b.e` の間で循環参照があってもエラーにはならない
    - `(*)` は、ドットを含まない任意の文字列にマッチし、 `(**)` はドットを含んだ任意の文字列とマッチする
    - したがって、 `a.(*)` は `a` 直下のパッケージにマッチし、 `a.(**)` は孫も含む `a` の下の全てのパッケージにマッチする
    - 詳しくは、 [PackageMatcher の Javadoc](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/base/PackageMatcher.html) を参照
- [beFreeOfCycles()](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/library/dependencies/syntax/SlicesShould.html#beFreeOfCycles()) で、循環参照が無いことを制約として定義している
    - `be free of chcles` は、「サイクル（循環参照）が無い」と翻訳するらしい[^1]（サイクルが自由＝サイクルして良い、かと思って最初混乱した）

[^1]: https://translate.google.co.jp/?hl=ja#view=home&op=translate&sl=auto&tl=ja&text=it%20should%20be%20free%20of%20cycles.

# PlantUML で描いた図を使ってチェックする
```text:パッケージ構成
`-src/
  |-main/java/
  | `-example/archunit/
  |   |-util/
  |   | `-UtilClass.java
  |   |-order/
  |   | `-OrderClass.java
  |   |-shipment/
  |   | `-ShipmentClass.java
  |   `-sales/
  |     `-SalesClass.java
  `-test/resources/
    `-component-diagram.pu
```

- `component-diagram.pu` という PlantUML のファイルが追加されている

```text:component-diagram.pu
@startuml ArchUnit

[受注] <<..order..>> as order
[出荷] <<..shipment..>> as shipment
[売上] <<..sales..>> as sales

order <-- shipment
shipment <-- sales

@enduml
```

![archunit.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/ea4d99ce-e5ff-db11-da80-ef9157aaddef.jpeg)

- PlantUML の [コンポーネント図](https://plantuml.com/ja/component-diagram) を使って、パッケージ間の依存関係を記述している
- ステレオタイプで、マッチさせるパッケージのパターンを設定しておく

```java:OrderClass.java
package example.archunit.order;

import example.archunit.util.UtilClass;

public class OrderClass {
    UtilClass utilClass;
}
```

```java:ShipmentClass.java
package example.archunit.shipment;

import example.archunit.order.OrderClass;

public class ShipmentClass {
    OrderClass orderClass;
}
```

```java:SalesClass.java
package example.archunit.sales;

import example.archunit.order.OrderClass;
import example.archunit.shipment.ShipmentClass;

public class SalesClass {
    ShipmentClass shipmentClass;
    OrderClass orderClass; // order パッケージに依存している
}
```

```java
package example;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.library.plantuml.PlantUmlArchCondition;
import org.junit.jupiter.api.Test;

import java.net.URL;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;
import static com.tngtech.archunit.library.plantuml.PlantUmlArchCondition.Configurations.*;
import static com.tngtech.archunit.library.plantuml.PlantUmlArchCondition.*;

public class ArchUnitTest {

    @Test
    void test1() {
        JavaClasses classes = new ClassFileImporter().importPackages("example.archunit");

        URL diagram = getClass().getResource("/component-diagram.pu");
        PlantUmlArchCondition condition = adhereToPlantUmlDiagram(diagram, consideringOnlyDependenciesInDiagram());

        ArchRule rule = classes().should(condition);

        rule.check(classes);
    }
}
```

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes should adhere to PlantUML diagram <component-diagram.pu> while ignoring dependencies not contained in the diagram' was violated (1 times):
Field <example.archunit.sales.SalesClass.orderClass> has type <example.archunit.order.OrderClass> in (SalesClass.java:0)
```

- [PlantUmlArchCondition.adhereToPlantUmlDiagram(...)](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/library/plantuml/PlantUmlArchCondition.html#adhereToPlantUmlDiagram(java.net.URL,com.tngtech.archunit.library.plantuml.PlantUmlArchCondition.Configuration)) メソッドを使用して PlantUML のテキストを読み込むことで、 PlantUML に記述した内容と実装を比較検証する `PlantUmlArchCondition` インスタンスを生成できる
    - `adhereToPlantUmlDiagram()` の第一引数には、読み込む PlantUML のファイルを渡す（`File`, `URL`, `Path` など）
    - 第二引数には、検証の範囲を絞り込む設定([PlantUmlArchCondition.Configuration](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/library/plantuml/PlantUmlArchCondition.Configurations.html))を渡す
    - `PlantUmlArchCondition.Configuration` に、以下３つのファクトリメソッドが用意されているので、そのいずれかを使用する
        - `consideringAllDependencies()`
            - 全てのクラス(`java.lang.Object` も含む)への依存を対象にして検証する
            - めっちゃシビアだけど、コンポーネントの描き忘れで検証を見落とすリスクは減る
        - `consideringOnlyDependenciesInDiagram()`
            - PlantUML の中に登場するコンポーネントへの依存を対象にして検証する
            - 前述の例では、 `OrderClass` は `UtilClass` に依存していたが、 PlantUML の図中に含まれていないので検証では特にエラーにもなっていなかった
            - コンポーネント図への記述を忘れると検証されない（見逃す恐れがある）、ということなので注意
        - `consideringOnlyDependenciesInAnyPackage(String, String...)`
            - PlantUML の中に登場し、かつ引数で指定したパターンにマッチするパッケージを対象にして検証する

# JUnit5 サポート
- `ClassFileImporter` によるクラスの読み込みは、プロジェクトの規模によっては時間がかかるようになり、サイズも大きくなる恐れがある
- ArchUnit には、一度読み込んだクラスの情報をキャッシュし、各テスト間で簡単に使いまわしできるようにする JUnit の拡張機能が用意されている
- JUnit4 もサポートされているが、ここでは JUnit5 で検証する

## Hello World
```text:パッケージ構成
`-example/archunit/
  |-foo/
  | `-Foo.java
  `-bar/
    `-Bar.java
```

```java:Foo.java
package example.archunit.foo;

import example.archunit.bar.Bar;

public class Foo {
    Bar bar;
}
```

```java:Bar.java
package example.archunit.bar;

public class Bar {}
```

```java
package example;

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

@AnalyzeClasses(packages = "example.archunit")
public class ArchUnitTest {
    @ArchTest
    static ArchRule fooDoesNotDependOnBar = noClasses().that().resideInAPackage("..foo..")
            .should().dependOnClassesThat().resideInAPackage("..bar..");
}
```

```text:実行結果
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package '..foo..' should depend on classes that reside in a package '..bar..'' was violated (1 times):
Field <example.archunit.foo.Foo.bar> has type <example.archunit.bar.Bar> in (Foo.java:0)
```

### 説明
```java
...
import com.tngtech.archunit.junit.AnalyzeClasses;
...
@AnalyzeClasses(packages = "example.archunit")
public class ArchUnitTest {
...
```

- まずは、テストクラスを [@AnalyzeClasses](https://javadoc.io/static/com.tngtech.archunit/archunit-junit/0.8.3/com/tngtech/archunit/junit/AnalyzeClasses.html) で注釈する
    - このアノテーションで、読み込む範囲を定義する
    - このアノテーションは、 `ClassFileImporter` の機能と対応している
    - ここでは `packages` で読み込むパッケージを指定している

```java
    @ArchTest
    static ArchRule fooDoesNotDependOnBar = noClasses().that().resideInAPackage("..foo..")
            .should().dependOnClassesThat().resideInAPackage("..bar..");
```

- 次に、通常と同じ用に `ArchRuleDefinition` を使って検証したい制約(`ArchRule`)を定義する
- 通常と異なるのは、作成した `ArchRule` を static フィールドに保存して、[@ArchTest](https://javadoc.io/static/com.tngtech.archunit/archunit-junit/0.8.3/com/tngtech/archunit/junit/ArchTest.html) で注釈している点
    - これにより、 `@AnalyzeClasses` で読み込まれたクラスに対して `ArchRule` の `check()` が自動的に実行されるようになる
    - static でなくても動くけど、テストケースごとにインスタンスを生成させる意味もないので static の方がスマートだと思う
    - 可視性は `public` でも `private` でも動いたので、何もつけなくて良さげ

## テストクラスを読み込まないようにする
- `ClassFileImporter` のときは、 `withImportOption()` に `ImportOption.Predefined.DO_NOT_INCLUDE_TESTS` を渡すことでテストクラスを読み込み対象から外すことができた
- `@AnalyzeClasses` にも、同じように `ImportOption` を指定する方法が用意されていて、以下のように設定する

```java
package example;

import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
...

@AnalyzeClasses(packages = "example.archunit", importOptions = ImportOption.DoNotIncludeTests.class)
public class ArchUnitTest {
    ...
}
```

- `importOptions` に、 `ImportOption` を実装したクラスの `Class` オブジェクトを指定する
- `ImportOption` には、 `Predefined` の定数と同じように `ImporOption` を実装したクラスがあらかじめ[メンバークラスとして定義されている](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/ImportOption.html#nested.class.summary)ので、それらを指定できる

## その他のクラスの読み込み方
- `packages` のパッケージ指定以外にも、以下のような指定方法が用意されている
- `packagesOf` による、 `Class` 指定の読み込み
    - `@AnalyzeClasses(packagesOf = {Foo.class, Bar.class})`
    - この場合、指定された `Class` が存在するパケージが読み込みの対象となる
    - この方法には、リファクタリングでパッケージ構成などが変わった場合に記述を修正しなくて済むというメリットがある（IDE でのリファクタリングが前提）
- [LocationProvider](https://javadoc.io/static/com.tngtech.archunit/archunit-junit/0.8.3/com/tngtech/archunit/junit/LocationProvider.html) による実装
    - `@AnalyzeClasses(locations = MyLocationProvider.class)`
    - `locations` に、 `LocationProvider` を実装したクラスの `Class` オブジェクトを渡す
    - `LocationProvider` の `get(Class)` メソッドでは、読み込み対象となる場所を [Location](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/com/tngtech/archunit/core/importer/Location.html) の `Set` で返すように実装する

## キャッシュの制御
- `@AnalyzeClasses` によって読み込まれたクラスはキャッシュされ、同じテストクラス内の各テストで再利用される
- また、 `@AnalyzeClasses` によって読み込まれたクラスは、デフォルトではそのテストクラスが終了したあともキャッシュされる
    - そして、別のテストクラスで読み込み場所の指定が同じ `@AnalyzeClasses` があったときは、キャッシュされた情報が再利用される
    - このキャッシュは、ヒープが足りなくなってきたときに破棄されるようになっている
- 読み込む対象が同じで使い回す方が効率がいい場合は、このデフォルトの動きで問題ない
- しかし、あるテストクラスでしか指定しない特別な読み込み場所だったりした場合は、他のテストクラスで再利用されることは無いので、キャッシュはヒープを圧迫するだけでむしろデメリットとなり得る
- そこで、次のように `@AnalyzeClasses` を設定することでキャッシュの生存期間を絞ることができるようになっている

```java
package example;

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.CacheMode;
...

@AnalyzeClasses(packages = "example.archunit", cacheMode = CacheMode.PER_CLASS)
public class ArchUnitTest {
    ...
}
```

- `cacheMode` に [CacheMode.PER_CLASS](https://javadoc.io/static/com.tngtech.archunit/archunit-junit/0.8.3/com/tngtech/archunit/junit/CacheMode.html#PER_CLASS) を指定することで、キャシュの生存期間をそのテストクラスの実行中だけに絞ることができる

## ルールのグループ化
```java:FooRules
package example;

import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class FooRules {
    @ArchTest
    static ArchRule fooDoesNotDependOnBar = noClasses().that().resideInAPackage("..foo..")
            .should().dependOnClassesThat().resideInAPackage("..bar..");
}
```

```java:BarRules
package example;

import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;

public class BarRules {
    @ArchTest
    static ArchRule classesInBarAreNotDeprecated = classes().that().resideInAnyPackage("..bar..")
            .should().notBeAnnotatedWith(Deprecated.class);
}
```

- クラスを `@AnalyzeClasses` で注釈せず、 `@ArchTest` で注釈したルールだけを宣言した `FooRules` と `BarRules` を用意する

```java
package example;

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchRules;
import com.tngtech.archunit.junit.ArchTest;

@AnalyzeClasses(packages = "example.archunit")
public class ArchUnitTest {
    @ArchTest
    static ArchRules fooRules = ArchRules.in(FooRules.class);
    
    @ArchTest
    static ArchRules barRules = ArchRules.in(BarRules.class);
}
```

- `ArchRules.in(Class)` を使ってルールだけを宣言したクラス（`FooRules`, `BarRules`）を読み込み、 `ArchRules` 型のフィールドに格納し、 `@ArchTest` で注釈する
- これを実行すれば、 `FooRules`, `BarRules` 内の全ての `@ArchTest` で注釈されたルールがテストされる
- このように、ルールをグループ化してまとめることができる
    - 例えば、 `ServiceLayerRules`, `DomainLayerRules` みたいな感じでまとめることができる
- 汎用的なルールであれば、様々なプロジェクトで使い回せるようにまとめることもできるかもしれない

# 参考
- [ArchUnit User Guide](https://www.archunit.org/userguide/html/000_Index.html)
- [archunit 0.14.1 javadoc (com.tngtech.archunit)](https://javadoc.io/static/com.tngtech.archunit/archunit/0.14.1/index.html)