「ArchUnit User Guide」を読む

概要
ArchUnit (v 1.0.0)のドキュメントを読む。
英語版のみ。

1. Introduction
ArchUnit is a free, simple and extensible library for checking the architecture of your Java code. That is, ArchUnit can check dependencies between packages and classes, layers and slices, check for cyclic dependencies and more. It does so by analyzing given Java bytecode, importing all classes into a Java code structure. ArchUnit’s main focus is to automatically test architecture and coding rules, using any plain Java unit testing framework.
ArchUnit は Java コードのアーキテクチャをチェックするための、フリーでシンプルかつ拡張可能なライブラリです。つまり、ArchUnit はパッケージとクラス、レイヤーとスライス間の依存関係をチェックし、周期的な依存関係をチェックすることなどができます。これは、与えられた Java バイトコードを解析し、全てのクラスを Java コード構造にインポートすることで行われます。ArchUnit はアーキテクチャやコーディングルールを自動的にテストすることに重点を置いており、プレーンな Java ユニットテストフレームワークを使用します。

1.1. Module Overview
Archunit には、5 つの module が存在する。
- archunit
- archunit-junit4
- archunit-junit5-api
- archunit-junit5-engine
- archunit-junit5-engine-api

1.1.1. Module archunit
これに包含されているのかと思ったらそうでもなかった、他の module が参照しているから、単体での import は不要かもしれない。
This module contains the actual ArchUnit core infrastructure required to write architecture tests: The ClassFileImporter, the domain objects, as well as the rule syntax infrastructure.
このモジュールにはアーキテクチャテストを書くために必要な実際の ArchUnit コアインフラストラクチャが含まれています。ClassFileImporter、ドメインオブジェクト、ルールシンタックスのインフラストラクチャです。

1.1.2. Module archunit-junit4
junit4 用の archunit。junit4 は使わないのでスルー。
This module contains the infrastructure to integrate with JUnit 4, in particular the ArchUnitRunner to cache imported classes.
このモジュールには JUnit 4 と統合するためのインフラストラクチャ、特にインポートされたクラスをキャッシュするための ArchUnitRunner が含まれています。

1.1.3. Modules archunit-junit5-*
junit5 用の archunit 。junit5 を使うので、最も使う。
These modules contain the infrastructure to integrate with JUnit 5 and contain the respective infrastructure to cache imported classes between test runs. archunit-junit5-api contains the user API to write tests with ArchUnit’s JUnit 5 support, archunit-junit5-engine contains the runtime engine to run those tests. archunit-junit5-engine-api contains API code for tools that want more detailed control over running ArchUnit JUnit 5 tests, in particular a FieldSelector which can be used to instruct the ArchUnitTestEngine to run a specific rule field (compare JUnit 4 & 5 Support).
archunit-junit5-api には ArchUnit の JUnit 5 サポートを使ったテストを書くためのユーザー API、archunit-junit5-engine にはそれらのテストを実行するためのランタイムエンジンが含まれています。archunit-junit5-engine-api には ArchUnit JUnit 5 テストの実行をより詳細に制御したいツールのための API コードが含まれており、特に特定のルールフィールドを実行するように ArchUnitTestEngine に指示するために使える FieldSelector があります(JUnit 4 & 5 Support を参照してください)。

1.1.4. Module archunit-example
サンプル用。
This module contains example architecture rules and sample code that violates these rules. Look here to get inspiration on how to set up rules for your project, or at ArchUnit-Examples for the last released version.
このモジュールにはアーキテクチャルールの例と、そのルールに違反するサンプルコードが含まれています。あなたのプロジェクトにルールを設定するためのヒントを得るにはここを、最後にリリースされたバージョンについては ArchUnit-Examples を参照してください。

2. Installation
Install 方法について。
To use ArchUnit, it is sufficient to include the respective JAR files in the classpath. Most commonly, this is done by adding the dependency to your dependency management tool, which is illustrated for Maven and Gradle below. Alternatively you can obtain the necessary JAR files directly from Maven Central.
ArchUnit を使うには、それぞれの JAR ファイルをクラスパスに含めれば十分です。最も一般的な方法は、依存関係管理ツールに依存関係を追加することです、以下は Maven と Gradle の例です。あるいは、Maven Central から必要な JAR ファイルを直接取得することもできます。

2.1. JUnit 4
JUnit4 なのでスキップ

2.2. JUnit 5
言っている意味はよくわからなかったけど、archunit-junit5 を導入すれば良い。
ArchUnit’s JUnit 5 artifacts follow the pattern of JUnit Jupiter. There is one artifact containing the API, i.e. the compile time dependencies to write tests. Then there is another artifact containing the actual TestEngine used at runtime. Just like JUnit Jupiter ArchUnit offers one convenience artifact transitively including both API and engine with the correct scope, which in turn can be added as a test compile dependency. Thus to include ArchUnit’s JUnit 5 support, simply add the following dependency from Maven Central:
ArchUnit の JUnit 5 のアーティファクトは JUnit Jupiter のパターンに従っています。API、つまりテストを書くためのコンパイル時の依存関係を含むアーティファクトが1つあります。そしてもうひとつは、実行時に使用する実際のTestEngineを含むアーティファクトです。JUnit Jupiter のように、ArchUnit は API とエンジンを正しいスコープで含む便利なアーティファクトを提供し、それをテストのコンパイル時の依存関係として追加することができるのです。ArchUnit の JUnit 5 サポートを含めるには、Maven Central から以下の依存関係を追加するだけです。
dependencies {
testImplementation 'com.tngtech.archunit:archunit-junit5:1.0.0'
}
dependencies {
testImplementation("com.tngtech.archunit:archunit-junit5:1.0.0")
}

2.3. Other Test Frameworks
Junit4 と Junit5 以外の Test Framework を使うときに、プレーンなarchunit を使うらしい。
ほとんどの場合において、ないと思うので、スキップ
ArchUnit works with any test framework that executes Java code. To use ArchUnit in such a context, include the core ArchUnit dependency from Maven Central:
ArchUnit は Java コードを実行するすべてのテストフレームワークで動作します。そのような状況で ArchUnit を使うには、Maven Central から ArchUnit のコア依存をインクルードします。

2.4. Maven Plugin
Maven からルールを指定できるらしい。maven を使う予定はないので、スキップ
There exists a Maven plugin by Société Générale to run ArchUnit rules straight from Maven. For more information visit their GitHub repo: https://github.com/societe-generale/arch-unit-maven-plugin
Société Générale による Maven プラグインがあり、Maven から直接 ArchUnit ルールを実行することができます。詳しくは GitHub リポジトリを参照してください: https://github.com/societe-generale/arch-unit-maven-plugin

3. Getting Started
とりあえず、技術的な始め方から。
考え方は、5. Ideas and Concepts を参照とのこと。
ArchUnit tests are written the same way as any Java unit test and can be written with any Java unit testing framework. To really understand the ideas behind ArchUnit, one should consult Ideas and Concepts. The following will outline a "technical" getting started.
ArchUnit のテストは他の Java ユニットテストと同じように書かれ、どの Java ユニットテストフレームワークでも書くことができます。ArchUnit の背後にある考えを本当に理解するためには、Ideas and Concepts を参照してください。以下は、「技術的な」始め方の概要です。

3.1. Importing Classes
Java コード構造の import は、ClassFileImporter によって実現されているらしい。
At its core ArchUnit provides infrastructure to import Java bytecode into Java code structures. This can be done using the ClassFileImporter
ArchUnit は Java バイトコードを Java コード構造にインポートするためのインフラストラクチャを提供します。これは ClassFileImporter を使って行うことができます。
JavaClasses classes = new ClassFileImporter().importPackages("com.mycompany.myapp");
The ClassFileImporter offers many ways to import classes. Some ways depend on the current project’s classpath, like importPackages(..). However there are other ways that do not, for example:
ClassFileImporterは、クラスをインポートするための多くの方法を提供します。いくつかの方法は importPackages(...) のように現在のプロジェクトのクラスパスに依存します。しかし、そうでない方法もあります。
JavaClasses classes = new ClassFileImporter().importPath("/some/path");
The returned object of type JavaClasses represents a collection of elements of type JavaClass, where JavaClass in turn represents a single imported class file. You can in fact access most properties of the imported class via the public API:
JavaClasses型の返されたオブジェクトは、JavaClass型の要素のコレクションを表し、ここでJavaClassは順番に1つのインポートクラスファイルを表します。実際には、パブリック API を介してインポートされたクラスのほとんどのプロパティにアクセスすることができます。
JavaClass clazz = classes.get(Object.class);
System.out.print(clazz.getSimpleName()); // returns 'Object'

3.2. Asserting (Architectural) Constraints
ルールの定義は、DSL を用意して、宣言的におこなう。
To express architectural rules, like 'Services should only be accessed by Controllers', ArchUnit offers an abstract DSL-like fluent API, which can in turn be evaluated against imported classes. To specify a rule, use the class ArchRuleDefinition as entry point:
サービスはコントローラからのみアクセスできるようにする" などのアーキテクチャ上のルールを表現するために、ArchUnit は抽象的な DSL のような流暢な API を提供しており、インポートしたクラスに対して評価することができます。ルールを指定するには、ArchRuleDefinition クラスをエントリポイントとして使用します。
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
// ...
ArchRule myRule = classes()
.that().resideInAPackage("..service..")
.should().onlyBeAccessed().byAnyPackage("..controller..", "..service..");
The two dots represent any number of packages (compare AspectJ Pointcuts). The returned object of type ArchRule can now be evaluated against a set of imported classes:
2つの点は、任意の数のパッケージを表します(AspectJ Pointcutsを参照)。返された ArchRule 型のオブジェクトは、インポートされたクラスのセットに対して評価することができます。
myRule.check(importedClasses);
Thus the complete example could look like
したがって、完全な例は次のようになります。
@Test
public void Services_should_only_be_accessed_by_Controllers() {
JavaClasses importedClasses = new ClassFileImporter().importPackages("com.mycompany.myapp");
ArchRule myRule = classes()
.that().resideInAPackage("..service..")
.should().onlyBeAccessed().byAnyPackage("..controller..", "..service..");
myRule.check(importedClasses);
}

3.3. Using JUnit 4 or JUnit 5
JUnit4 と JUnit5 拡張が用意されているため。
@AnalyzeClasses
で Archunit の評価対象を選択して、評価できる。
@ArchUnitRunner
は JUnit4 のとき必要。
詳細な、JUnit の拡張については、JUnit Support を参照とのこと。
While ArchUnit can be used with any unit testing framework, it provides extended support for writing tests with JUnit 4 and JUnit 5. The main advantage is automatic caching of imported classes between tests (of the same imported classes), as well as reduction of boilerplate code.
ArchUnit はどんなユニットテストフレームワークでも使うことができますが、JUnit 4 と JUnit 5 でテストを書くための拡張サポートを提供します。主な利点は、テスト間でインポートされたクラス(同じインポートクラス)を自動的にキャッシュすることと、定型的なコードを削減することです。
To use the JUnit support, declare ArchUnit’s ArchUnitRunner (only JUnit 4), declare the classes to import via @AnalyzeClasses and add the respective rules as fields:
JUnit サポートを使うには、ArchUnit の ArchUnitRunner (JUnit 4 のみ) を宣言し、@AnalyzeClasses を通してインポートするクラスを宣言し、それぞれのルールをフィールドとして追加します。
@RunWith(ArchUnitRunner.class) // Remove this line for JUnit 5!!
@AnalyzeClasses(packages = "com.mycompany.myapp")
public class MyArchitectureTest {
@ArchTest
public static final ArchRule myRule = classes()
.that().resideInAPackage("..service..")
.should().onlyBeAccessed().byAnyPackage("..controller..", "..service..");
}
The JUnit test support will automatically import (or reuse) the specified classes and evaluate any rule annotated with @ArchTest against those classes.
JUnitのテストサポートは、指定されたクラスを自動的にインポート(または再利用)し、それらのクラスに対して@ArchTestでアノテーションされたルールを評価します。
For further information on how to use the JUnit support refer to JUnit Support.
JUnitサポートの使用方法の詳細については、JUnitサポート を参照してください。

3.4. Using JUnit support with Kotlin
Kotlin 対応の場合は以下で実行する。
評価の仕方は、2 種類あって、フィールドに式を書いて評価するパターンと、関数に切り出して、rule.check
で評価するパターン。
@AnalzeClasses
の pacakgeOf で書いてあるけど、全てのクラスを指定しないといけないのか?
@RunWith(ArchUnitRunner::class) // Remove this line for JUnit 5!!
@AnalyzeClasses(packagesOf = [MyArchitectureTest::class])
class MyArchitectureTest {
@ArchTest
val rule_as_field = ArchRuleDefinition.noClasses().should()...
@ArchTest
fun rule_as_method(importedClasses: JavaClasses) {
val rule = ArchRuleDefinition.noClasses().should()...
rule.check(importedClasses)
}
}

4. What to Check
チェックできることの紹介。画像つきじゃないとわかりづらいから、ドキュメントに飛んだ方がいいかも。
The following section illustrates some typical checks you could do with ArchUnit.
次のセクションでは、ArchUnit でできる典型的なチェックを説明します。

4.1. Package Dependency Checks
package の依存を注意する。
以下は、source
package から、foo
package へのアクセスを禁止するパターン。
日本語に訳すと、
- クラスは存在しない
- なにを
- source package に配置されている
- するべき
- クラスに対して
- foo package に配置する
noClasses().that().resideInAPackage("..source..")
.should().dependOnClassesThat().resideInAPackage("..foo..")
「foo package に存在するクラスは、soruce.one
pacakge または、foo
package のみから依存される」
classes().that().resideInAPackage("..foo..")
.should().onlyHaveDependentClassesThat().resideInAnyPackage("..source.one..", "..foo..")

4.2. Class Dependency Checks
Class の依存関係。
「「.*Bar」は「Bar」を呼び出すことが許される。」
classes().that().haveNameMatching(".*Bar")
.should().onlyHaveDependentClassesThat().haveSimpleName("Bar")

4.3. Class and Package Containment Checks
クラスと package の関係。
「Foo から始まる、クラスは com.foo
package にのみ存在する」
classes().that().haveSimpleNameStartingWith("Foo")
.should().resideInAPackage("com.foo")

4.4. Inheritance Checks
継承(or 実装)の依存確認。
「Connection interface を実装した Class の名前は Connection で終わる」
classes().that().implement(Connection.class)
.should().haveSimpleNameEndingWith("Connection")
???
classes().that().areAssignableTo(EntityManager.class)
.should().onlyHaveDependentClassesThat().resideInAnyPackage("..persistence..")

4.5. Annotation Checks
「EntityManager クラスはTransactional アノテーションを持ったクラスからのみ、アクセスされる」
classes().that().areAssignableTo(EntityManager.class)
.should().onlyHaveDependentClassesThat().areAnnotatedWith(Transactional.class)

4.6. Layer Checks
レイヤーの責務を補償する。
- 「Controller 層は
controller
package、Service 層はservice
package、Persistence 層はpersistence
package かと定義する」 - 「Controller層 はどこからのレイヤからもアクセスされない」
- 「Service 層は Controller 層のみからアクセスされる」
- 「Persistence 層は Service 層のみからアクセスされる」
layeredArchitecture()
.consideringAllDependencies()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Persistence").definedBy("..persistence..")
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
.whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service")

4.7. Cycle Checks
`com.myapp package に循環参照は存在しない」
slices().matching("com.myapp.(*)..").should().beFreeOfCycles()

5. Ideas and Concepts
ArchUnit is divided into different layers, where the most important ones are the "Core" layer, the "Lang" layer and the "Library" layer. In short the Core layer deals with the basic infrastructure, i.e. how to import byte code into Java objects. The Lang layer contains the rule syntax to specify architecture rules in a succinct way. The Library layer contains more complex predefined rules, like a layered architecture with several layers. The following section will explain these layers in more detail.
ArchUnitは様々なレイヤーに分かれており、最も重要なものは「Core」レイヤー、「Lang」レイヤー、「Library」レイヤーです。簡単に言うと、Core層は基本的なインフラストラクチャ、つまりバイトコードをJavaオブジェクトにインポートする方法を扱います。Lang層には、アーキテクチャーのルールを簡潔に指定するためのルール構文が含まれています。Library層には、より複雑な事前定義されたルールが含まれており、いくつかの層を持つレイヤーアーキテクチャのようなものです。次のセクションでは、これらの層についてより詳しく説明します。

5.1. Core
class file の import について?
Much of ArchUnit’s core API resembles the Java Reflection API. There are classes like JavaMethod, JavaField, and more, and the public API consists of methods like getName(), getMethods(), getRawType() or getRawParameterTypes(). Additionally ArchUnit extends this API for concepts needed to talk about dependencies between code, like JavaMethodCall, JavaConstructorCall or JavaFieldAccess. For example, it is possible to programmatically iterate over javaClass.getAccessesFromSelf() and react to the imported accesses between this Java class and other Java classes.
ArchUnit のコア API の多くは、Java Reflection API に似ています。JavaMethod や JavaField などのクラスがあり、パブリック API は getName(), getMethods(), getRawType() や getRawParameterTypes() などのメソッドで構成されています。さらに ArchUnit は JavaMethodCall, JavaConstructorCall, JavaFieldAccess などのコード間の依存性を語るために必要な概念のためにこの API を拡張しています。例えば、プログラム上で javaClass.getAccessesFromSelf() を繰り返し実行し、この Java クラスと他の Java クラスの間のインポートアクセスに反応することが可能です。
To import compiled Java class files, ArchUnit provides the ClassFileImporter, which can for example be used to import packages from the classpath:
コンパイルされた Java クラスファイルをインポートするために、ArchUnit は ClassFileImporter を提供し、例えばクラスパスからパッケージをインポートするのに使うことができます。
JavaClasses classes = new ClassFileImporter().importPackages("com.mycompany.myapp");
For more information refer to The Core API.
詳しくは、The Core APIをご覧ください。

5.2. Lang
The Core API is quite powerful and offers a lot of information about the static structure of a Java program. However, tests directly using the Core API lack expressiveness, in particular with respect to architectural rules.
For this reason ArchUnit provides the Lang API, which offers a powerful syntax to express rules in an abstract way. Most parts of the Lang API are composed as fluent APIs, i.e. an IDE can provide valuable suggestions on the possibilities the syntax offers.
An example for a specified architecture rule would be:
Core API は非常に強力で、Java プログラムの静的構造に関する多くの情報を提供します。しかし、Core API を直接使用したテストは、特にアーキテクチャのルールに関して表現力に欠けています。
このため、ArchUnit はルールを抽象的に表現するための強力なシンタックスを提供する Lang API を提供します。Lang API のほとんどの部分は流れるような API として構成されており、IDE はこの構文が提供する可能性について有益な提案をすることができる。
指定されたアーキテクチャ・ルールの例は次のようになる。
ArchRule rule =
classes().that().resideInAPackage("..service..")
.should().onlyBeAccessed().byAnyPackage("..controller..", "..service..");
Once a rule is composed, imported Java classes can be checked against it:
一度ルールを構成すれば、インポートされたJavaクラスはそのルールと照合することができる。
JavaClasses importedClasses = new ClassFileImporter().importPackage("com.myapp");
ArchRule rule = // define the rule
rule.check(importedClasses);
The syntax ArchUnit provides is fully extensible and can thus be adjusted to almost any specific need. For further information, please refer to The Lang API.
ArchUnit が提供する構文は完全に拡張可能であり、ほぼすべての特定のニーズに合わせることができます。詳しくは The Lang API を参照してください。

5.3. Library
The Library API offers predefined complex rules for typical architectural goals. For example a succinct definition of a layered architecture via package definitions. Or rules to slice the code base in a certain way, for example in different areas of the domain, and enforce these slices to be acyclic or independent of each other. More detailed information is provided in The Library API.
ライブラリAPIは、典型的なアーキテクチャの目標に対して、あらかじめ定義された複雑なルールを提供する。例えば、パッケージ定義によるレイヤードアーキテクチャの簡潔な定義がある。あるいは、コードベースをある方法でスライスし、例えばドメインの異なる領域で、これらのスライスを非周期的または互いに独立であるように強制するルール。より詳細な情報は、The Library APIに記載されている。

6. The Core API
The Core API is itself divided into the domain objects and the actual import.
Core APIは、それ自体がドメインオブジェクトと実際のインポートに分かれています。

6.1. Import
As mentioned in Ideas and Concepts the backbone of the infrastructure is the ClassFileImporter, which provides various ways to import Java classes. One way is to import packages from the classpath, or the complete classpath via
アイデアとコンセプトで述べたように、インフラのバックボーンはClassFileImporterであり、Javaクラスをインポートする様々な方法を提供します。1つの方法は、クラスパスからパッケージをインポートする方法と、クラスパス全体を
JavaClasses classes = new ClassFileImporter().importClasspath();
However, the import process is completely independent of the classpath, so it would be well possible to import any path from the file system:
しかし、インポート処理はクラスパスとは完全に独立しているので、ファイルシステムから任意のパスをインポートすることは十分に可能である。
JavaClasses classes = new ClassFileImporter().importPath("/some/path/to/classes");
The ClassFileImporter offers several other methods to import classes, for example locations can be specified as URLs or as JAR files.
ClassFileImporterには、他にもクラスをインポートする方法がいくつかあります。例えば、場所をURLやJARファイルとして指定することができます。
Furthermore specific locations can be filtered out, if they are contained in the source of classes, but should not be imported. A typical use case would be to ignore test classes, when the classpath is imported. This can be achieved by specifying ImportOptions:
さらに、クラスのソースに含まれているがインポートすべきではない場合、特定の場所をフィルタリングすることができます。典型的な使用例としては、クラスパスがインポートされたときにテストクラスを無視することでしょう。これは、ImportOption を指定することによって実現できます。
ImportOption ignoreTests = new ImportOption() {
@Override
public boolean includes(Location location) {
return !location.contains("/test/"); // ignore any URI to sources that contains '/test/'
}
};
JavaClasses classes = new ClassFileImporter().withImportOption(ignoreTests).importClasspath();
A Location is principally an URI, i.e. ArchUnit considers sources as File or JAR URIs
ArchUnit はソースを File や JAR URI とみなしています。
- file:///home/dev/my/project/target/classes/some/Thing.class
- jar:file:///home/dev/.m2/repository/some/things.jar!/some/Thing.class
For the two common cases to skip importing JAR files and to skip importing test files (for typical setups, like a Maven or Gradle build), there already exist predefined ImportOptions:
JARファイルのインポートをスキップする、およびテストファイルのインポートをスキップするという2つの一般的なケース(MavenやGradleビルドのような典型的なセットアップ)については、定義済みのImportOptionがすでに存在しています。
new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importClasspath();

6.1.1. Dealing with Missing Classes
While importing the requested classes (e.g. target/classes or target/test-classes) it can happen that a class within the scope of the import has a reference to a class outside of the scope of the import. This will naturally happen, if the classes of the JDK are not imported, since then for example any dependency on Object.class will be unresolved within the import.
要求されたクラス (例: target/classes または target/test-classes) をインポートしているときに、インポートの範囲内のクラスがインポートの範囲外のクラスへの参照を持っていることが起こり得ます。これは、JDK のクラスがインポートされていない場合に自然に発生する現象で、たとえば Object.class への依存はインポート内で未解決になります。
At this point ArchUnit needs to decide how to treat these classes that are missing from the import. By default, ArchUnit searches within the classpath for missing classes and if found imports them. This obviously has the advantage that information about those classes (which interfaces they implement, how they are annotated) is present during rule evaluation.
このとき、ArchUnit はインポートされなかったクラスをどのように扱うか決定する必要があります。デフォルトでは、ArchUnit はクラスパス内で見つからないクラスを検索し、見つかった場合はそれらをインポートします。これには明らかに、ルール評価時にこれらのクラスに関する情報 (どのインターフェイスを実装しているか、どのようにアノテーションされているか) が存在するという利点があります。
On the downside this additional lookup from the classpath will cost some performance and in some cases might not make sense (e.g. if information about classes not present in the original import is known to be unnecessary for evaluating rules). Thus ArchUnit can be configured to create stubs instead, i.e. a JavaClass that has all the known information, like the fully qualified name or the method called. However, this stub might naturally lack some information, like superclasses, annotations or other details that cannot be determined without importing the bytecode of this class. This behavior will also happen, if ArchUnit fails to determine the location of a missing class from the classpath.
このとき、ArchUnit はインポートされなかったクラスをどのように扱うか決定する必要があります。デフォルトでは、ArchUnit はクラスパス内で見つからないクラスを検索し、見つかった場合はそれらをインポートします。これには明らかに、ルール評価時にこれらのクラスに関する情報 (どのインターフェイスを実装しているか、どのようにアノテーションされているか) が存在するという利点があります。
To find out, how to configure the default behavior, refer to Configuring the Resolution Behavior.
デフォルトの動作を設定する方法については、「解決動作を設定する」を参照してください。

6.2. Domain
わからないのでスキップ
The domain objects represent Java code, thus the naming should be pretty straight forward. Most commonly, the ClassFileImporter imports instances of type JavaClass. A rough overview looks like this:
ドメイン・オブジェクトはJavaコードを表すので、命名はかなり単純であるべきです。最も一般的なのは、ClassFileImporterがJavaClass型のインスタンスをインポートすることです。大まかな概要はこのようになります。
Most objects resemble the Java Reflection API, including inheritance relations. Thus a JavaClass has JavaMembers, which can in turn be either JavaField, JavaMethod, JavaConstructor (or JavaStaticInitializer). While not present within the reflection API, it makes sense to introduce an expression for anything that can access other code, which ArchUnit calls 'code unit', and is in fact either a method, a constructor (including the class initializer) or a static initializer of a class (e.g. a static { … } block, a static field assignment, etc.).
ほとんどのオブジェクトは、継承関係も含めて、Java Reflection APIに似ています。したがって、JavaClassはJavaMembersを持ち、それは順番にJavaField、JavaMethod、JavaConstructor (またはJavaStaticInitializer) のいずれかになることができます。Reflection API にはありませんが、ArchUnit が 'コードユニット' と呼ぶ、他のコードにアクセスできるもののための式を導入することは意味があり、実際にはメソッド、コンストラクタ (クラス初期化を含む) またはクラスの静的初期化 (static { ... } ブロック、static field assignment など) のいずれかであることがわかります。
Furthermore one of the most interesting features of ArchUnit that exceeds the Java Reflection API, is the concept of accesses to another class. On the lowest level accesses can only take place from a code unit (as mentioned, any block of executable code) to either a field (JavaFieldAccess), a method (JavaMethodCall) or constructor (JavaConstructorCall).
さらに、Java Reflection API を超える ArchUnit の最も興味深い機能の1つは、別のクラスへのアクセスの概念です。最も低いレベルでは、コードユニット(前述のように実行可能なコードのブロック)からフィールド(JavaFieldAccess)、メソッド(JavaMethodCall)、コンストラクタ(JavaConstructorCall)のいずれかにのみアクセスすることができます。
ArchUnit imports the whole graph of classes and their relationship to each other. While checking the accesses from a class is pretty isolated (the bytecode offers all this information), checking accesses to a class requires the whole graph to be built first. To distinguish which sort of access is referred to, methods will always clearly state fromSelf and toSelf. For example, every JavaField allows to call JavaField#getAccessesToSelf() to retrieve all code units within the graph that access this specific field. The resolution process through inheritance is not completely straight forward. Consider for example
ArchUnit はクラスとその関係のグラフ全体をインポートします。クラスからのアクセスをチェックするのはとても簡単ですが (バイトコードがこの情報をすべて提供します)、クラスへのアクセスをチェックするには、まずグラフ全体を構築する必要があります。どのような種類のアクセスが参照されているかを区別するために、メソッドは常にfromSelfとtoSelfを明確に記述します。例えば、すべてのJavaFieldは、この特定のフィールドにアクセスするグラフ内のすべてのコードユニットを取得するために、JavaField#getAccessesToSelf()を呼び出すことを許可します。継承による解決プロセスは、完全に単純なものではありません。例えば
The bytecode will record a field access from ClassAccessing.accessField() to ClassBeingAccessed.accessedField. However, there is no such field, since the field is actually declared in the superclass. This is the reason why a JavaFieldAccess has no JavaField as its target, but a FieldAccessTarget. In other words, ArchUnit models the situation, as it is found within the bytecode, and an access target is not an actual member within another class. If a member is queried for accessesToSelf() though, ArchUnit will resolve the necessary targets and determine, which member is represented by which target. The situation looks roughly like
バイトコードでは、ClassAccessing.accessField() から ClassBeingAccessed.accessedField へのフィールドアクセスが記録されます。しかし、実際にはフィールドはスーパークラスで宣言されているため、そのようなフィールドは存在しません。これが、JavaFieldAccessのターゲットがJavaFieldではなく、FieldAccessTargetである理由です。言い換えると、ArchUnit はバイトコード内の状況をモデル化しており、アクセスターゲットは他のクラス内の実際のメンバではありません。もしメンバーが accessesToSelf() でクエリされた場合、ArchUnit は必要なターゲットを解決し、どのメンバーがどのターゲットによって表現されるかを決定します。大まかには以下のような状況です

7. The Lang API
宣言的に記述できる API についてさしていそう。

7.1. Composing Class Rules
宣言的にかくことを目的にした。
ArchUnit の ..
は sub package も許容している。

7.2. Composing Member Rules
ArchRuleDefinition
でJava クラスのメンバに対してもルールを適用できる。
ArchRuleDefinition が持っているメソッド。
- methods()
- members()
- fields()
- codeUnits()
- constructors()
- nomembers()
- noFields()
- noMethods

7.3. Creating Custom Rules
全ての archunit のルールは以下で表現している。
classes that ${PREDICATE} should ${CONDITION}
これらは、DescribedPredicate と ArchCondition の 2 つで表現されていて、デフォルトのメソッドやクラスはこれらで表現している。
DescribedPredicate<JavaClass> resideInAPackageService = // define the predicate
ArchCondition<JavaClass> accessClassesThatResideInAPackageController = // define the condition
noClasses().that(resideInAPackageService)
.should(accessClassesThatResideInAPackageController);
そのため、自作するには、以下のように定義する。
DescribedPredicate<JavaClass> haveAFieldAnnotatedWithPayload =
new DescribedPredicate<JavaClass>("have a field annotated with @Payload"){
@Override
public boolean apply(JavaClass input) {
boolean someFieldAnnotatedWithPayload = // iterate fields and check for @Payload
return someFieldAnnotatedWithPayload;
}
};
ArchCondition<JavaClass> onlyBeAccessedBySecuredMethods =
new ArchCondition<JavaClass>("only be accessed by @Secured methods") {
@Override
public void check(JavaClass item, ConditionEvents events) {
for (JavaMethodCall call : item.getMethodCallsToSelf()) {
if (!call.getOrigin().isAnnotatedWith(Secured.class)) {
String message = String.format(
"Method %s is not @Secured", call.getOrigin().getFullName());
events.add(SimpleConditionEvent.violated(call, message));
}
}
}
};
classes().that(haveAFieldAnnotatedWithPayload).should(onlyBeAccessedBySecuredMethods);

7.4 Predefined Predicates and Conditions
よくわからないので、スキップ

7.5. Rules with Custom Concepts

8. The Library API

9. JUnit Support
JUnit Support についての章。
以下はシンプルに書いた例。
@Test
public void rule1() {
JavaClasses importedClasses = new ClassFileImporter().importClasspath();
ArchRule rule = classes()...
rule.check(importedClasses);
}
@Test
public void rule2() {
JavaClasses importedClasses = new ClassFileImporter().importClasspath();
ArchRule rule = classes()...
rule.check(importedClasses);
}
ただし、大きなプロジェクトで、明示的に check
をおこなうと時間がかかるらしい。

9.1. JUnit 4 & 5 Support

9.1.1. Writing tests
書き方。3 つめの書き方は cache が残る。
@RunWith(ArchUnitRunner.class) // Remove this line for JUnit 5!!
@AnalyzeClasses(packages = "com.myapp")
public class ArchitectureTest {
// ArchRules can just be declared as static fields and will be evaluated
@ArchTest
public static final ArchRule rule1 = classes().should()...
@ArchTest
public static final ArchRule rule2 = classes().should()...
@ArchTest
public static void rule3(JavaClasses classes) {
// The runner also understands static methods with a single JavaClasses argument
// reusing the cached classes
}
}