Spring Modulithにおけるモジュール間の依存関係は、アプリケーションのモジュール構造を明確に定義し、アーキテクチャの整合性を確保するための重要な概念です。モジュール間の依存関係を適切に管理することで、モジュールの独立性を高め、変更の影響範囲を限定し、アプリケーションの保守性と拡張性を向上させることができます。
Spring Modulithは、モジュール間の依存関係を制御するための強力な機能を提供します。デフォルトでは、モジュールは他のモジュールの内部実装に依存することはできず、明示的に公開されたAPIを通じてのみ依存関係を持つことができます。これにより、モジュールの内部実装の詳細が他のモジュールに漏れることを防ぎ、モジュールの独立性を確保します。
+------------------------------------------+ | Spring Boot Application | | | | +-------------+ +-------------+ | | | | | | | | | Module A |------>| Module B | | | | | | (API) | | | +-------------+ +-------------+ | | | | +-------------+ +-------------+ | | | | | | | | | Module C |------>| Module D | | | | | | (API) | | | +-------------+ +-------------+ | | | | | | +-------------+ | | +-------->| | | | | Module E | | | | (API) | | | +-------------+ | | | | Spring Modulith Framework | +------------------------------------------+
図1: Spring Modulithにおけるモジュール間の依存関係 - モジュールは公開APIを通じてのみ依存関係を持つ
Spring Modulithでは、モジュール間の依存関係を管理するために、以下の機能を提供しています:
モジュールの公開APIは、他のモジュールが依存することができるインターフェースやクラスを定義します。Spring Modulithでは、@org.springframework.modulith.ApplicationModule.API
アノテーションを使用して、公開APIを明示的に定義することができます。
package com.example.application.orders.api;
import org.springframework.modulith.ApplicationModule;
@ApplicationModule.API
public interface OrderService {
// 公開APIメソッド
Order createOrder(OrderRequest request);
Order getOrder(String orderId);
List<Order> getOrdersByCustomer(String customerId);
}
このアノテーションを付けたクラスやインターフェースは、他のモジュールからアクセス可能な公開APIとして扱われます。公開APIは、モジュールの境界を越えて他のモジュールに提供される機能を明確に定義し、モジュールの内部実装の詳細を隠蔽します。
より細かい粒度でモジュールの公開APIを定義するために、@org.springframework.modulith.NamedInterface
アノテーションを使用することができます。このアノテーションを使用すると、特定のパッケージを名前付きインターフェースとして定義し、他のモジュールがそのインターフェースに依存することを許可できます。
@org.springframework.modulith.NamedInterface("api")
package com.example.application.orders.api;
import org.springframework.modulith.NamedInterface;
この例では、com.example.application.orders.api
パッケージを「api」という名前のインターフェースとして定義しています。他のモジュールは、このインターフェースに依存することができます。
モジュール間の依存関係に制約を設けるために、@org.springframework.modulith.ApplicationModule
アノテーションのallowedDependencies
属性を使用することができます。この属性を使用すると、モジュールが依存することができる他のモジュールを明示的に指定できます。
@org.springframework.modulith.ApplicationModule(
allowedDependencies = { "inventory", "customers" }
)
package com.example.application.orders;
import org.springframework.modulith.ApplicationModule;
この例では、「orders」モジュールが「inventory」モジュールと「customers」モジュールにのみ依存することを許可しています。他のモジュールへの依存は禁止されます。
Spring Modulithは、モジュール間の依存関係を検証するための機能を提供します。Modulith
クラスのverify()
メソッドを使用すると、アプリケーションのモジュール構造を検証し、不正な依存関係がないかをチェックできます。
@Test
void verifyModuleDependencies() {
// モジュール間の依存関係の検証
new Modulith(Application.class)
.verify()
.assertThatDependenciesAreValid();
}
このテストは、アプリケーションのモジュール構造を検証し、不正な依存関係がある場合にテストが失敗します。これにより、モジュール間の依存関係の整合性を継続的に確保することができます。
Spring Modulithでは、モジュール間の依存関係を管理するために、以下のパターンを活用することができます:
直接依存パターンでは、モジュールAがモジュールBの公開APIに直接依存します。このパターンは、モジュール間の関係が明確で、直接的な依存関係が必要な場合に適しています。
// モジュールA内のコード
@Service
class OrderService {
private final InventoryService inventoryService;
OrderService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
@Transactional
public Order createOrder(Order order) {
// 在庫の確認
boolean inStock = inventoryService.checkStock(order.getItems());
if (!inStock) {
throw new OutOfStockException();
}
// 注文の処理
// ...
return order;
}
}
この例では、「orders」モジュール内のOrderService
が「inventory」モジュールのInventoryService
に直接依存しています。
イベント駆動パターンでは、モジュール間の直接的な依存関係を避け、イベントを通じて通信します。このパターンは、モジュール間の疎結合性を高め、モジュールの独立性を確保するために適しています。
// モジュールA内のコード
@Service
class OrderService {
private final ApplicationEventPublisher events;
OrderService(ApplicationEventPublisher events) {
this.events = events;
}
@Transactional
public Order createOrder(Order order) {
// 注文の処理
// ...
// イベントの発行
events.publishEvent(new OrderCreatedEvent(order));
return order;
}
}
// モジュールB内のコード
@Service
class InventoryService {
@EventListener
@Transactional
void on(OrderCreatedEvent event) {
// 在庫の更新
// ...
}
}
この例では、「orders」モジュール内のOrderService
がOrderCreatedEvent
を発行し、「inventory」モジュール内のInventoryService
がそのイベントを購読しています。これにより、「orders」モジュールは「inventory」モジュールに直接依存することなく、通信することができます。
共有カーネルパターンでは、複数のモジュールが共有するドメインモデルやユーティリティを定義します。このパターンは、モジュール間で共通の概念や機能を共有する必要がある場合に適しています。
// 共有カーネルモジュール内のコード
package com.example.application.shared;
public class Money {
private final BigDecimal amount;
private final Currency currency;
// コンストラクタ、メソッドなど
}
// モジュールA内のコード
@Service
class OrderService {
@Transactional
public Order createOrder(Order order) {
// Money クラスの使用
Money totalPrice = calculateTotalPrice(order.getItems());
order.setTotalPrice(totalPrice);
// 注文の処理
// ...
return order;
}
}
// モジュールB内のコード
@Service
class PaymentService {
@Transactional
public Payment processPayment(Order order) {
// Money クラスの使用
Money amount = order.getTotalPrice();
// 支払いの処理
// ...
return payment;
}
}
この例では、「shared」モジュール内のMoney
クラスが「orders」モジュールと「payments」モジュールで共有されています。
Spring Modulithは、モジュール間の依存関係を可視化するための機能を提供します。Modulith
クラスのtoModuleModel()
メソッドを使用すると、アプリケーションのモジュール構造をモデル化し、そのモデルを使用して依存関係を可視化できます。
@Test
void createModulithDiagram() {
// モジュール構造のモデル化
ModuleModel model = new Modulith(Application.class).toModuleModel();
// PlantUML図の生成
new PlantUmlModuleWriter().write(model, Paths.get("modulith-diagram.puml"));
// AsciiDoc図の生成
new AsciiDocModuleWriter().write(model, Paths.get("modulith-diagram.adoc"));
}
このコードは、アプリケーションのモジュール構造をPlantUML図とAsciiDoc図として生成します。これらの図を使用して、モジュール間の依存関係を視覚的に理解することができます。
Spring Modulithは、モジュール間の依存関係をテストするための機能を提供します。@ModuleTest
アノテーションを使用すると、特定のモジュールとその依存モジュールのみをロードしてテストを実行できます。
@SpringBootTest
@ModuleTest(value = "orders")
class OrderModuleTests {
@Autowired OrderService orders;
@Test
void testCreateOrder() {
// テストコード
// ...
}
}
このテストは、「orders」モジュールとその依存モジュールのみをロードしてテストを実行します。これにより、モジュール間の依存関係が適切に定義されていることを確認できます。
Spring Modulithにおけるモジュール間の依存関係の管理は、モジュラーアプリケーションの成功に不可欠な要素です。適切な依存関係の管理により、モジュールの独立性を高め、変更の影響範囲を限定し、アプリケーションの保守性と拡張性を向上させることができます。
Spring Modulithは、モジュール間の依存関係を管理するための強力な機能を提供します。公開APIの定義、名前付きインターフェースの定義、依存関係の制約の定義、依存関係の検証など、様々な機能を活用することで、アーキテクチャの整合性を確保しながら、モジュール間の適切な依存関係を構築することができます。
モジュール間の依存関係を適切に管理するためには、明確な公開APIの定義、最小限の公開API、依存関係の方向性の管理、イベント駆動通信の活用、共有カーネルの適切な使用、依存関係の検証の自動化、依存関係の可視化などのベストプラクティスを実践することが重要です。これらのプラクティスを実践することで、モジュラーアプリケーションの品質と持続可能性を向上させることができます。