6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Modulithでモジュラモノリスライクな依存関係のコントロールを試してみた

Posted at

Spring Modulithとは

Spring Modulith は、Spring Boot を使用してドメイン駆動型のモジュール型アプリケーションを構築するための独自のツールキットです。Spring Boot がアプリケーションの技術的な配置について独自の見解を持っているのと同じように、Spring Modulith は、アプリを関数に構造化する方法について独自の見解を実装し、個々の論理部分が相互にやり取りできるようにします。その結果、Spring Modulith を使用すると、開発者は更新が容易なアプリケーションを構築して、時間の経過とともに変化するビジネス要件に対応できるようになります。

本記事の内容

Spring Modulith の依存関係をコントロールする機能を試してみた記録です。
先日参加したJJUG CCC 2024 Fallで紹介されていて興味が湧き試してみました◎

他にもアプリケーションモジュールのドキュメント化やテスト実行に関する機能もありますが、今回は依存関係のコントロールに絞った内容となります。

コントロールできる依存関係

  • 公開パッケージ以外の参照の禁止
  • 循環参照の禁止

デフォルトの制御としては上記が行われ、必要に応じてカスタマイズすることができます。
デフォルトの制限を守ることでモジュラモノリスライクな依存関係のコントロールが実現できます。

試してみる

テスト用アプリケーションの構成

一つのアプリケーションの中に inventory:在庫order:注文 という2つのモジュールが存在し、それぞれのモジュールが疎結合なモジュラモノリスアプリケーションを作成したい場合を考えます。

image.png

com.example.demo.DemoApplication.java がSpringBootのApplicationクラスです。
Applicationクラスを配置したパッケージのサブパッケージである inventoryorder が各モジュールのAPIパッケージとして認識されます。

Spring ModulithではこのAPIパッケージ以下が一つのモジュールとして扱われ、直下のクラスのみが他のモジュールから参照可能になります。
そのため inventoryorder パッケージの直下には他のモジュールから参照されることを想定したインターフェースやオブジェクトクラスを配置します。

それぞれのモジュールには entity repository service のようなサブパッケージがありますが、これらのパッケージ以下のクラスは他のモジュールから参照することができません。この制御によってモジュール間の依存が整理されたアプリケーションを構築することができます。

検査の実行

以下のようなテストを書いて実行します。
依存関係の違反があった場合にはこのテストがfailします。
テストを実行する必要があるため、実際にプロダクト開発に検査の仕組みを組み込むためにはCIでテストを実行する仕組みが必要になります。

package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.modulith.core.ApplicationModules;

public class DemoApplicationDependenciesTest {
    @Test
    public void test() {
        ApplicationModules.of(DemoApplication.class).verify();
    }
}

公開パッケージ以外の参照の禁止

公開パッケージ以外への参照として、OrderService から InventoryRepository を直接参照するという不届きな実装をしてみます。

package com.example.demo.order.service;
import com.example.demo.inventory.Inventory;
import com.example.demo.inventory.repository.InventoryRepository;

public class OrderService {
    
    private InventoryRepository inventoryRepository;

    public OrderService(InventoryRepository inventoryRepository) {
        this.inventoryRepository = inventoryRepository;
    }

    public void buy(String productId, int quantity) {
        List<Inventory> allInventory = inventoryRepository.loadAllInventory();
    }
}

この実装で DemoApplicationDependenciesTest はFailします。

image.png

org.springframework.modulith.core.Violations: - Module 'order' depends on non-exposed type
com.example.demo.inventory.repository.InventoryRepository within module 'inventory'!

order module が inventory module の公開されていない型にアクセスしている、というエラーが表示されます。

次に公開されているパッケージへの参照を実装してみます。

package com.example.demo.order.service;
import com.example.demo.inventory.IInventoryModule;
import com.example.demo.inventory.Inventory;

public class OrderService {

    private IInventoryModule inventoryModule;

    public OrderService(IInventoryModule inventoryModule) {
        this.inventoryModule = inventoryModule;
    }

    public void buy(String productId, int quantity) {
        Inventory orderProductInventory = inventoryModule.loadInventory(productId);
    }
}

この実装で DemoApplicationDependenciesTest はPassします。

image.png

このように公開されているパッケージ以外へのアクセスを禁止することができます。

循環参照の禁止

上記の OrderServiceIInventoryModule を参照している状態で InventoryServiceIOrderModule を参照するような実装をしてみます。

package com.example.demo.inventory.service;

import org.springframework.stereotype.Service;
import com.example.demo.order.IOrderModule;

@Service
public class InventoryService {
    
    private IOrderModule orderModule;

    public InventoryService(IOrderModule orderModule) {
        this.orderModule = orderModule;
    }
}

この実装はお互いの公開されているパッケージのクラスにのみアクセスしていますが、モジュールが相互依存しているため DemoApplicationDependenciesTest はFailします。

image.png

org.springframework.modulith.core.Violations: - Cycle detected: Slice inventory

循環参照しているというエラーが表示されます。

カスタマイズ

特定のパッケージ以下を他モジュールから参照可能にする

例えばどのモジュールでも共通で利用するユーティリティーなどを以下のように配置したい場合があると思います。

image.png

こういった時は common パッケージの package-info.java を以下のように記述すると、common以下のパッケージが参照可能になります。

@org.springframework.modulith.ApplicationModule(
  type = Type.OPEN
)
package com.example.demo.common;

import org.springframework.modulith.ApplicationModule.Type;

特定のパッケージだけを他モジュールから参照可能にする

特定のパッケージ以下を全て、ではなく特定のパッケージ直下だけを参照可能にしたい場合があると思います。
例えば以下の SpiInterface は他モジュールから参照可能にしたいが、 spi.repository パッケージ以下のクラスは参照可能にしたくない場合。

image.png

こういった場合は spi パッケージの package-info.java を以下のように記述すると、spiパッケージ直下のクラスのみが参照可能になります。

@org.springframework.modulith.NamedInterface("spi")
package com.example.demo.inventory.spi;

依存できるモジュールを指定して制限する

以下のように

参照の図.drawio.png

orderinventory のみを参照できる。
inventorypurchase:仕入れ のみを参照できる。
という制限を行いたい場合は各々の package-info.java を以下のようにすると実現できます。

orderpackage-info.java

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

inventorypackage-info.java

@ApplicationModule(allowedDependencies = "purchase")
package com.example.demo.inventory;

purchasepackage-info.java

@org.springframework.modulith.ApplicationModule(allowedDependencies = "purchase")
package com.example.demo.purchase;

purchaseは自分自身のみを allowedDependencies に指定することで他のモジュールを参照できなくしています。
このように参照を許可するモジュールを指定することで、許可されていないモジュールには公開されているパッケージも参照できないように制御することができます。

また orderinventorypurchase の両方を参照できるようにしたい、というような場合は複数指定することで制御できます。

@org.springframework.modulith.ApplicationModule(allowedDependencies = {"inventory","purchase"})
package com.example.demo.order;

おわり。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?