この記事内で扱うサンプルコードでは、Maven 3, Java 17, Spring Boot 3.0.0 を使用しています。
0. Spring Modulith でのパッケージの扱いについて
各機能を見ていく前に Spring Modulith におけるパッケージの扱いについて、説明しておきます。
Spring Modulith では、メインアプリケーションクラスが存在するパッケージ直下のサブパッケージをアプリケーションモジュールと呼び、あるドメインに対応するモジュールとして扱います。
そして、アプリケーションモジュール直下の public なクラスは、そのモジュールの公開インターフェースとして扱われます。
また、アプリケーションモジュール配下のサブパッケージは、他のモジュールからアクセスされない内部的なものとして扱われます。
org.springframework.modulith.model.Violations: - Cycle detected: Slice inventory ->
Slice order ->
Slice inventory
1. Dependencies of Slice inventory
- Constructor <com.example.samplemodulith.inventory.InventoryService.<init>(com.example.samplemodulith.order.OrderService, com.example.samplemodulith.inventory.internal.InternalInventoryComponent)> has parameter of type<com.example.samplemodulith.order.OrderService>in(InventoryService.java:0)
- Field <com.example.samplemodulith.inventory.InventoryService.orderService> has type<com.example.samplemodulith.order.OrderService>in(InventoryService.java:0)2. Dependencies of Slice order
- Constructor <com.example.samplemodulith.order.OrderService.<init>(com.example.samplemodulith.inventory.InventoryService, org.springframework.context.ApplicationEventPublisher)> has parameter of type<com.example.samplemodulith.inventory.InventoryService>in(OrderService.java:0)
- Field <com.example.samplemodulith.order.OrderService.inventoryService> has type<com.example.samplemodulith.inventory.InventoryService>in(OrderService.java:0)
org.springframework.modulith.model.Violations: - Module 'order' depends on non-exposed type com.example.samplemodulith.inventory.internal.InternalInventoryComponent within module 'inventory'!
OrderService declares constructor OrderService(InternalInventoryComponent, ApplicationEventPublisher)in(OrderService.java:0)
- Module 'order' depends on non-exposed type com.example.samplemodulith.inventory.internal.InternalInventoryComponent within module 'inventory'!
Field <com.example.samplemodulith.order.OrderService.internalInventoryComponent> has type<com.example.samplemodulith.inventory.internal.InternalInventoryComponent>in(OrderService.java:0)
- Module 'order' depends on non-exposed type com.example.samplemodulith.inventory.internal.InternalInventoryComponent within module 'inventory'!
Constructor <com.example.samplemodulith.order.OrderService.<init>(com.example.samplemodulith.inventory.internal.InternalInventoryComponent, org.springframework.context.ApplicationEventPublisher)> has parameter of type<com.example.samplemodulith.inventory.internal.InternalInventoryComponent>in(OrderService.java:0)
内部パッケージの中には、同一モジュールの公開インタフェースからの参照があるために public になるクラスもありますが、これらのクラスが別モジュールからアクセスされた場合に、テストで検知できるのはとても助かりますね。
Order モジュールが Inventory モジュールに依存しており、Order モジュールの結合テストでは InventoryService クラスが注入されていることを確認できます。
Bootstrapping @ModuleTest for order in mode DIRECT_DEPENDENCIES (class com.example.samplemodulith.SampleModulithApplication)…
=============================================================================================================================## order ##> Logical name: order
> Base package: com.example.samplemodulith.order
> Direct module dependencies: inventory
> Spring beans:
+ ….OrderService
=============================================================================================================================
Included dependencies:
=============================================================================================================================## inventory ##> Logical name: inventory
> Base package: com.example.samplemodulith.inventory
> Direct module dependencies: none
> Spring beans:
+ ….InventoryService
+ ….internal.InternalInventoryComponent
=============================================================================================================================
Bootstrapping @ModuleTest for order in mode STANDALONE (class com.example.samplemodulith.SampleModulithApplication)…
====================================================================================================================## order ##> Logical name: order
> Base package: com.example.samplemodulith.order
> Direct module dependencies: inventory
> Spring beans:
+ ….OrderService
Order モジュールだけでテストが実行できていることが確認できました。
3. イベントによるモジュール同士の連携
よりモジュール同士を疎結合にするために、Spring Modulith ではモジュール間のやりとりに Spring Application Events を使うことを推奨しています。
モジュール間のやり取りにイベントを用いることで、呼び出し元が呼び出し先について知る必要がなくなり、テスト時も呼び出し先の Spring Bean に依存/モックする必要がなくなります。
Kafka などのメッセージングシステムを使用することに似ていますが、Spring Application Events は Spring Framework が提供しているため、追加の依存関係/インフラは必要ありません。
Bootstrapping @ModuleTest for order in mode STANDALONE (class com.example.samplemodulith.SampleModulithApplication)…
====================================================================================================================## order ##> Logical name: order
> Base package: com.example.samplemodulith.order
> Direct module dependencies: none
> Spring beans:
+ ….OrderService