基礎

Spring Modulith は、Spring Boot アプリケーションに論理モジュールを実装する開発者をサポートします。これにより、開発者は構造検証を適用し、モジュールの配置をドキュメント化し、個々のモジュールの統合テストを実行し、実行時にモジュールの相互作用を観察し、一般的にモジュールの相互作用を疎結合方式で実装できます。このセクションでは、技術サポートに進む前に開発者が理解する必要がある基本的な概念について説明します。

アプリケーションモジュール

Spring Boot アプリケーションでは、アプリケーションモジュールは次の部分で構成される機能の単位です。

  • Spring Bean インスタンスによって実装される他のモジュールに公開される API、およびモジュールによって公開されるアプリケーションイベント。通常は、提供されたインターフェースと呼ばれます。

  • 他のモジュールからアクセスされることが想定されていない内部実装コンポーネント。

  • Spring Bean 依存関係、リッスンされるアプリケーションイベント、および公開される構成プロパティの形式で他のモジュールによって公開される API への参照。通常、必須インターフェースと呼ばれます。

Spring Modulith は、Spring Boot アプリケーション内でモジュールを表現するさまざまな方法を提供します。主に、全体的な配置に含まれる複雑さのレベルが異なります。これにより、開発者はシンプルなものから始めて、必要に応じて自然に高度な方法に移行できます。

ApplicationModules 型

Spring Modulith を使用すると、コードベースをインスペクションして、指定された配置とオプションの構成に基づいてアプリケーションモジュールモデルを導出できます。spring-modulith-core アーティファクトには、Spring Boot アプリケーションクラスを指すことができる ApplicationModules が含まれています。

アプリケーションモジュールモデルの作成
  • Java

  • Kotlin

var modules = ApplicationModules.of(Application.class);
val modules = ApplicationModules.of(Application::class.java)

modules には、コードベースから派生したアプリケーションモジュール配置のメモリ内表現が含まれます。モジュールとして検出される部分は、クラスが属するパッケージの Java パッケージ構造によって異なります。シンプルなアプリケーションモジュールでデフォルトで期待される配置について詳しく確認してください。高度な配置とカスタマイズオプションについては、高度なアプリケーションモジュールとで説明されています。

分析された配置がどのように見えるかを把握するには、モデル全体に含まれる個々のモジュールをコンソールに書き込むだけです。

アプリケーションモジュールの配置をコンソールに書き込む
  • Java

  • Kotlin

modules.forEach(System.out::println);
modules.forEach { println(it) }
アプリケーションモジュール構成のコンソール出力
## example.inventory ##
> Logical name: inventory
> Base package: example.inventory
> Spring beans:
  + ….InventoryManagement
  o ….SomeInternalComponent

## example.order ##
> Logical name: order
> Base package: example.order
> Spring beans:
  + ….OrderManagement
  + ….internal.SomeInternalComponent

各モジュールがどのようにリストされ、含まれる Spring コンポーネントが識別され、それぞれの可視性もレンダリングされるかに注目してください。

パッケージの除外

アプリケーションモジュールインスペクションから特定の Java クラスまたは完全なパッケージを除外する場合は、次のようにします。

  • Java

  • Kotlin

ApplicationModules.of(Application.class, JavaClass.Predicates.resideInAPackage("com.example.db")).verify();
ApplicationModules.of(Application::class.java, JavaClass.Predicates.resideInAPackage("com.example.db")).verify()

除外の追加例:

  • com.example.db — 指定されたパッケージ com.example.db 内のすべてのファイルと一致します。

  • com.example.db.. — 指定されたパッケージ (com.example.db) およびすべてのサブパッケージ (com.example.db.a または com.example.db.b.c) 内のすべてのファイルと一致します。

  • ..example.. —  a.examplea.example.ba.b.example.c.d に一致しますが、a.exam.b には一致しません。

可能なマッチャーの詳細については、ArchUnit PackageMatcher [GitHub] (英語) の JavaDoc を参照してください。

シンプルなアプリケーションモジュール

アプリケーションのメインパッケージは、メインアプリケーションクラスが存在するパッケージです。つまり、@SpringBootApplication のアノテーションが付けられたクラスであり、通常はその実行に使用される main(…) メソッドが含まれています。デフォルトでは、メインパッケージの各直接サブパッケージは、アプリケーションモジュールパッケージとみなされます。

このパッケージにサブパッケージが含まれていない場合、単純なパッケージとみなされます。Java のパッケージスコープを使用して、他のパッケージに存在するコードから型が参照されないようにすることで、そのパッケージ内のコードを非表示にすることができ、その結果、他のパッケージへの依存性注入の影響を受けなくなります。当然のことながら、モジュールの API はパッケージ内のすべてのパブリック型で構成されます。

配置例を見てみましょう ( パブリック型を示します。 パッケージプライベートのもの)。

単一のインベントリアプリケーションモジュール
 Example
└─  src/main/java
   ├─  example                        (1)
   |  └─  Application.java
   └─  example.inventory              (2)
      ├─  InventoryManagement.java
      └─  SomethingInventoryInternal.java
1 アプリケーションのメインパッケージ example
2 アプリケーションモジュールパッケージ inventory

高度なアプリケーションモジュール

アプリケーションモジュールパッケージにサブパッケージが含まれている場合、まったく同じモジュールのコードから参照できるように、サブパッケージ内の型をパブリックにする必要がある場合があります。

在庫およびオーダーアプリケーションモジュール
 Example
└─  src/main/java
   ├─  example
   |  └─  Application.java
   ├─  example.inventory
   |  ├─  InventoryManagement.java
   |  └─  SomethingInventoryInternal.java
   ├─  example.order
   |  └─  OrderManagement.java
   └─  example.order.internal
      └─  SomethingOrderInternal.java

このような配置では、order パッケージは API パッケージと見なされます。他のアプリケーションモジュールのコードは、その中の型を参照できます。order.internal は、アプリケーションモジュールベースパッケージの他のサブパッケージと同様に、内部パッケージと見なされます。それらの中のコードは、他のモジュールから参照できません。SomethingOrderInternal がパブリック型である点に注意してください。これは、おそらく OrderManagement がそれに依存しているためです。残念ながら、これは inventory などの他のパッケージからも参照できることを意味します。この場合、Java コンパイラーは、これらの不正な参照を防ぐのにあまり役立ちません。

ネストされたアプリケーションモジュール

バージョン 1.3 以降、Spring Modulith アプリケーションモジュールにはネストされたモジュールを含めることができます。これにより、モジュールに論理的に分離される部分が含まれている場合に、内部構造を管理できます。ネストされたアプリケーションモジュールを定義するには、@ApplicationModule で構成するパッケージに明示的にアノテーションを付けます。

 Example
└─  src/main/java
   |
   ├─  example
   |  └─  Application.java
   |
   |  -> Inventory
   |
   ├─  example.inventory
   |  ├─  InventoryManagement.java
   |  └─  SomethingInventoryInternal.java
   ├─  example.inventory.internal
   |  └─  SomethingInventoryInternal.java
   |
   |  -> Inventory > Nested
   |
   ├─  example.inventory.nested
   |  ├─  package-info.java // @ApplicationModule
   |  └─  NestedApi.java
   ├─  example.inventory.nested.internal
   |  └─  NestedInternal.java
   |
   |  -> Order
   |
   └─  example.order
      ├─  OrderManagement.java
      └─  SomethingOrderInternal.java

この例では、inventory は前述のアプリケーションモジュールです。nested パッケージの @ApplicationModule アノテーションにより、ネストされたアプリケーションモジュールになりました。この配置では、次のアクセスルールが適用されます。

  • Nested 内のコードは、Inventory または Inventory 内にネストされた兄弟アプリケーションモジュールによって公開される任意の型からのみ使用できます。

  • ネストされたモジュール内のコードは、内部であっても親モジュール内のコードにアクセスできます。つまり、NestedApi と NestedInternal はどちらも inventory.internal.SomethingInventoryInternal にアクセスできます。

  • ネストされたモジュールのコードも、最上位のアプリケーションモジュールによって公開された型にアクセスできます。nested (または任意のサブパッケージ) 内のすべてのコードは、OrderManagement にアクセスできます。

オープンアプリケーションモジュール

上記の配置は、公開対象としてアクティブに選択された他のモジュールにのみ型を公開するため、クローズドであると見なされます。Spring Modulith をレガシーアプリケーションに適用する場合、ネストされたパッケージにあるすべての型を他のモジュールから非表示にすることは不十分であるか、それらのすべてのパッケージを公開対象としてマークする必要が生じる可能性があります。

アプリケーションモジュールをオープンモジュールに変換するには、package-info.java 型の @ApplicationModule アノテーションを使用します。

アプリケーションモジュールをオープンとして宣言する
  • Java

  • Kotlin

@org.springframework.modulith.ApplicationModule(
  type = Type.OPEN
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
  type = Type.OPEN
)
package example.inventory

アプリケーションモジュールをオープンとして宣言すると、検証に次の変更が加えられます。

  • 通常、他のモジュールからアプリケーションモジュールの内部型へのアクセスは許可されます。

  • すべての型、さらにはアプリケーションモジュールベースパッケージのサブパッケージに存在する型も、名前付きインターフェースに明示的に割り当てられていない限り、名前のない名前付きインターフェースに追加されます。

この機能は、既存のプロジェクトのコードベースを Spring Modulith 推奨のパッケージ構造に徐々に移行する際に主に使用することを目的としています。完全にモジュール化されたアプリケーションでは、オープンアプリケーションモジュールを使用すると、通常、モジュール化とパッケージ構造が最適ではなくなります。

アプリケーションモジュールの明示的な依存関係

モジュールは、package-info.java ファイルを通じて表されるパッケージの @ApplicationModule アノテーションを使用して、許可された依存関係を宣言することを選択できます。たとえば、Kotlin はそのファイルをサポートしていないため、アプリケーションモジュールのルートパッケージにある単一の型にアノテーションを使用することもできます。

モジュールの依存関係を明示的に構成するインベントリ
  • Java

  • Kotlin

@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order"
)
package example.inventory;
package example.inventory

import org.springframework.modulith.ApplicationModule

@ApplicationModule(allowedDependencies = "order")
class ModuleMetadata {}

この場合、在庫モジュール内のコードは、オーダーモジュール内のコードを参照することのみが許可されていました (そして、コードはそもそもどのモジュールにも割り当てられていませんでした)。アプリケーションモジュール構造の検証でそれを監視する方法について調べましょう。

名前付きインターフェース

デフォルトでは、高度なアプリケーションモジュールに従って、アプリケーションモジュールの基本パッケージは API パッケージと見なされ、他のモジュールからの受信依存関係を許可する唯一のパッケージになります。追加のパッケージを他のモジュールに公開する場合は、名前付きインターフェースを使用する必要があります。これは、それらのパッケージの package-info.java ファイルに @NamedInterface または明示的に @org.springframework.modulith.PackageInfo でアノテーションを付けることで実現します。

SPI 名前付きインターフェースをカプセル化するパッケージの配置
 Example
└─  src/main/java
   ├─  example
   |  └─  Application.java
   ├─ …
   ├─  example.order
   |  └─  OrderManagement.java
   ├─  example.order.spi
   |  ├—  package-info.java
   |  └─  SomeSpiInterface.java
   └─  example.order.internal
      └─  SomethingOrderInternal.java
package-info.java の example.order.spi
  • Java

  • Kotlin

@org.springframework.modulith.NamedInterface("spi")
package example.order.spi;
package example.order.spi

import org.springframework.modulith.PackageInfo
import org.springframework.modulith.NamedInterface

@PackageInfo
@NamedInterface("spi")
class ModuleMetadata {}

この宣言の効果は 2 つあります。まず、他のアプリケーションモジュールのコードが SomeSpiInterface を参照できるようになります。アプリケーションモジュールは、明示的な依存関係宣言で名前付きインターフェースを参照できます。インベントリーモジュールがそれを利用していると仮定すると、上記で宣言された名前付きインターフェースを次のように参照できます。

専用の名前付きインターフェースへの許可された依存関係の定義
  • Java

  • Kotlin

@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order :: spi"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order :: spi"
)
package example.inventory

名前付きインターフェースの名前 spi を二重コロン :: で連結する方法に注目してください。この設定では、インベントリ内のコードは、SomeSpiInterface および order.spi インターフェースに存在する他のコードに依存することが許可されますが、たとえば OrderManagement には依存しません。依存関係が明示的に記述されていないモジュールの場合、アプリケーションモジュールのルートパッケージと SPI パッケージの両方にアクセスできます。

アプリケーションモジュールが明示的に宣言されたすべての名前付きインターフェースを参照できることを表現する場合は、次のようにアスタリスク (*) を使用できます。

アスタリスクを使用して、宣言されたすべての名前付きインターフェースへの許可された依存関係を宣言する
  • Java

  • Kotlin

@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order :: *"
)
package example.inventory;
@org.springframework.modulith.ApplicationModule(
  allowedDependencies = "order :: *"
)
package example.inventory

アプリケーションモジュールの配置のカスタマイズ

Spring Modulith を使用すると、メインの Spring Boot アプリケーションクラスで使用される @Modulithic アノテーションを介して作成するアプリケーションモジュール配置に関するいくつかのコアアスペクトを構成できます。

  • Java

  • Kotlin

package example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.modulith.Modulithic;

@Modulithic
@SpringBootApplication
class MyApplication {

  public static void main(String... args) {
    SpringApplication.run(MyApplication.class, args);
  }
}
package example

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.modulith.Modulithic

@Modulithic
@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
  runApplication<MyApplication>(*args)
}

アノテーションはカスタマイズするために次の属性を公開します。

アノテーション属性 説明

systemName

生成されたドキュメントで使用されるアプリケーションの人間が読める名前。

sharedModules

指定された名前のアプリケーションモジュールを共有モジュールとして宣言します。つまり、それらのモジュールはアプリケーションモジュール統合テストに常に含まれます。

additionalPackages

構成されたパッケージを追加のルートアプリケーションパッケージとして扱うように Spring Modulith に指示します。つまり、それらに対してもアプリケーションモジュールの検出がトリガーされます。

モジュール検出のカスタマイズ

デフォルトでは、アプリケーションモジュールは、Spring Boot アプリケーションクラスが存在するパッケージの直接のサブパッケージに配置されるものと想定されます。Spring Modulith の @ApplicationModule または jMolecules の @Module アノテーションを介して明示的にアノテーションが付けられたパッケージのみを考慮するように、代替の検出戦略をアクティブ化できます。この戦略は、spring.modulith.detection-strategy を explicitly-annotated に構成することでアクティブ化できます。

アプリケーションモジュール検出戦略を、アノテーション付きパッケージのみを考慮するように切り替えます
spring.modulith.detection-strategy=explicitly-annotated

デフォルトのアプリケーションモジュール検出戦略も手動でアノテーションを付けた戦略もアプリケーションで機能しない場合は、ApplicationModuleDetectionStrategy の実装を提供することでモジュールの検出をカスタマイズできます。そのインターフェースは単一のメソッド Stream<JavaPackage> getModuleBasePackages(JavaPackage) を公開し、Spring Boot アプリケーションクラスが存在するパッケージで呼び出されます。次に、その中に存在するパッケージをインスペクションし、命名規則などに基づいてアプリケーションモジュールベースパッケージと見なすパッケージを選択できます。

次のようにカスタム ApplicationModuleDetectionStrategy 実装を宣言するとします。

カスタム ApplicationModuleDetectionStrategy の実装
  • Java

  • Kotlin

package example;

class CustomApplicationModuleDetectionStrategy implements ApplicationModuleDetectionStrategy {

  @Override
  public Stream<JavaPackage> getModuleBasePackages(JavaPackage basePackage) {
    // Your module detection goes here
  }
}
package example

class CustomApplicationModuleDetectionStrategy : ApplicationModuleDetectionStrategy {

  override fun getModuleBasePackages(basePackage: JavaPackage): Stream<JavaPackage> {
    // Your module detection goes here
  }
}

このクラスは、次のように spring.modulith.detection-strategy として登録できます。

spring.modulith.detection-strategy=example.CustomApplicationModuleDetectionStrategy

モジュールの検証とドキュメントをカスタマイズするために ApplicationModuleDetectionStrategy インターフェースを実装する場合は、カスタマイズとその登録をアプリケーションのテストソースに含めます。ただし、Spring Modulith ランタイムコンポーネント (ApplicationModuleInitializer など、またはアクチュエーターや可観測性サポートなどの本番対応機能 ) を使用している場合は、コンパイル時の依存関係として以下を明示的に宣言する必要があります。

<dependency>
  <groupId>org.springframework.modulith</groupId>
  <artifactId>spring-modulith-core</artifactId>
</dependency>
dependencies {
  implementation 'org.springframework.modulith:spring-modulith-core'
}

他のパッケージからのアプリケーションモジュールの提供

@Modulithic では、アノテーション付きクラスのパッケージ以外のパッケージに対してアプリケーションモジュール検出をトリガーするように additionalPackages を定義できますが、その使用には事前にそれらについて知っておく必要があります。バージョン 1.3 以降、Spring Modulith は、ApplicationModuleSource および ApplicationModuleSourceFactory 抽象化を介してアプリケーションモジュールの外部貢献をサポートしています。後者の実装は、META-INF にある spring.factories ファイルに登録できます。

org.springframework.modulith.core.ApplicationModuleSourceFactory=example.CustomApplicationModuleSourceFactory

このようなファクトリは、ApplicationModuleDetectionStrategy を適用するために任意のパッケージ名を返すことも、モジュールを作成するためにパッケージを明示的に返すこともできます。

package example;

public class CustomApplicationModuleSourceFactory implements ApplicationModuleSourceFactory {

  @Override
  public List<String> getRootPackages() {
    return List.of("com.acme.toscan");
  }

  @Override
  public ApplicationModuleDetectionStrategy getApplicationModuleDetectionStrategy() {
    return ApplicationModuleDetectionStrategy.explicitlyAnnotated();
  }

  @Override
  public List<String> getModuleBasePackages() {
    return List.of("com.acme.module");
  }
}

上記の例では、com.acme.toscan を使用して、その中で明示的に宣言されたモジュールを検出し、com.acme.module からアプリケーションモジュールを作成します。これらから返されるパッケージ名は、その後、ApplicationModuleDetectionStrategy で公開されている対応する getApplicationModuleSource(…) フレーバーを介して ApplicationModuleSource に変換されます。