Java

Spring Modulithとは?モジュラーモノリスの構造検証・イベント連携・テストを実装コードで解説【2026年版】

Spring Modulithは、Spring Bootアプリケーションを1つのデプロイ単位のまま複数のモジュールに分割し、その境界をコードで検証・強制するための公式プロジェクトです。マイクロサービスのようにサービスを物理分割せず、パッケージ単位でモジュールを区切り、循環依存や不正な参照をビルド時のテストで弾けます。最新の安定版は2026年6月11日にGAとなった2.1.0で、Spring Boot 4.1/Spring Framework 7系に対応します。本記事は、モジュールの定義から構造検証・モジュール間イベント連携・結合テスト・ドキュメント生成までを、実際に動く実装コードと最新バージョンの事実にもとづいて解説します。

まとめ:Spring Modulithで押さえるべき要点

先に結論を示します。Spring Modulithは「マイクロサービスに分けるほどではないが、モノリスの中に秩序を持たせたい」規模で最も効きます。

  • 正体:Spring Bootのメインパッケージ直下のサブパッケージを1モジュールとして扱い、モジュール境界をApplicationModules.of(App.class).verify()で検証する仕組み。
  • 最新版:2.1.0(2026-06-11 GA、Spring Boot 4.1/Framework 7)。Spring Boot 3.x環境では1.4系(並行リリースの1.4.12)を使う。
  • 3つの柱:構造検証(循環依存の禁止)、モジュール間の疎結合イベント連携(Event Publication Registry)、モジュール単位の結合テスト(@ApplicationModuleTest)。
  • 移行の橋渡し@ExternalizedでイベントをKafka/AMQP等へ外部化でき、将来サービスを切り出す際の下準備になる。

以下で、導入から各機能の実装までを順に見ていきます。

Spring Modulithの位置づけとバージョン対応

Spring Modulithが解くのは「モノリスは肥大化すると内部の依存が絡まり、どこを変えると何が壊れるか分からなくなる」という問題です。マイクロサービスはこれをサービス分割で解決しますが、ネットワーク通信・独立DB・分散トランザクションという運用コストを伴います。Spring Modulithは、単一プロセス・単一DBのまま論理的なモジュール境界を引き、その境界をテストで守るという中間解を提供します。

モジュラーモノリスとマイクロサービスの違い

両者の差はデプロイと通信の単位にあります。モジュラーモノリスの詳しい概念はモジュラモノリスとは何か?その基本概念と特徴を解説で整理していますが、Spring Modulithの理解に必要な範囲を表にまとめます。

観点 モジュラーモノリス(Spring Modulith) マイクロサービス
デプロイ単位 1プロセス・1デプロイ サービスごとに独立
モジュール間通信 メソッド呼び出し/アプリ内イベント ネットワーク越しのAPI
データ 単一DB・プロセス内トランザクション サービスごとDB・結果整合
境界の強制 verify()で自動検証 ネットワークで物理分離
運用の複雑さ 低い 高い

ネットワーク分割の代わりにコンパイル時・テスト時の検証で境界を担保するのがSpring Modulithの発想です。境界を守れているモジュールは、必要になったときにサービスとして切り出しやすくなります。

バージョンとSpring Boot対応

バージョン選定はSpring Boot世代で決まります。公式ドキュメント末尾の互換性表は更新漏れがあるため、リリースブログとMaven Centralの実POMを基準にした対応を示します。

Spring Modulith 対応Spring Boot 位置づけ
2.1.x 4.1(Framework 7) 最新GA(2026-06-11)
2.0.x 4.0(Framework 7) Boot 4系ベースライン(2025-11-21 GA)
1.4.x 3.3〜3.5 Boot 3系メンテナンス(並行1.4.12)

2.1.0の実依存はMaven Central上でspring-core 7.0.8spring-boot-autoconfigure 4.1.0です。つまりSpring Boot 4.1へ上げたプロジェクトは2.1系、まだSpring Boot 3.x(Spring Bootバージョン一覧とサポート期限参照)に留まるプロジェクトは1.4系を選びます。1.0のGAは2023年8月21日で、それ以前は実験的プロジェクトでした。Spring Boot 4の変更点はSpring Boot 4とは?最新バージョン4.1の変更点にまとめています。

導入手順(BOMとスターター)

バージョンはBOMで一元管理し、用途に応じたスターターを追加します。まず最小構成(コア+テスト)は次のとおりです。

<!-- pom.xml -->
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.modulith</groupId>
      <artifactId>spring-modulith-bom</artifactId>
      <version>2.1.0</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>org.springframework.modulith</groupId>
    <artifactId>spring-modulith-starter-core</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.modulith</groupId>
    <artifactId>spring-modulith-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

永続化や外部化を使う場合は、目的別スターターを足します。イベントを未処理のまま失わないための永続化にはspring-modulith-starter-jpa-jdbc-mongodb-neo4jのいずれか、Actuator連携にはspring-modulith-starter-insightを使います。個別のアーティファクト(spring-modulith-events-kafka等)はBOMがバージョンを補うため、依存側でversion指定は不要です。

モジュールの定義とパッケージ構造

Spring Modulithのモジュールは、設定ファイルではなくパッケージ構造そのものです。@SpringBootApplicationが置かれたメインパッケージの直下にあるサブパッケージが、それぞれ1つのアプリケーションモジュールとして自動認識されます。

com.example.shop
├─ ShopApplication.java      @SpringBootApplication
├─ order                     order モジュール
│   ├─ Order.java            公開型(他モジュールから参照可)
│   └─ internal              下位パッケージ=非公開
│       └─ OrderProcessor.java
└─ inventory                 inventory モジュール
    └─ InventoryService.java

モジュールのbaseパッケージ(例ではorder)直下のpublic型が公開API、order.internalのような下位パッケージの型は他モジュールから参照できません。baseパッケージ以外も公開したいときは@NamedInterface("spi")で名前付きインターフェースとして追加公開します。さらに、あるモジュールが依存してよい相手をpackage-info.javaで明示的に絞れます。

@ApplicationModule(allowedDependencies = "order :: spi")
package com.example.shop.inventory;

import org.springframework.modulith.ApplicationModule;

allowedDependencies::区切りで「モジュール名 :: 名前付きインターフェース名」を指定し、"order :: *"で全公開範囲を許可します。宣言しなければ既定でどのモジュールにも依存できますが、明示することで意図しない依存の追加をテストで検知できるようになります。

モジュール構造の検証

境界が守られているかは、テスト1本で機械的に判定します。ApplicationModules.of(...)でモジュール構成を解析し、verify()で循環依存・非公開型への参照・allowedDependencies違反を一括検出します。

class ModularityTests {

  @Test
  void verifiesModularStructure() {
    ApplicationModules.of(ShopApplication.class).verify();
  }
}

このテストをCIに載せておけば、誰かがinternalパッケージの型を別モジュールから呼び出した瞬間にビルドが失敗します。人手のレビューに頼らず、モジュール境界という設計意図をコードで守れる点がSpring Modulithの中核価値です。違反を条件付きで扱いたいときは、例外を投げる前にフィルタできます。

ApplicationModules.of(ShopApplication.class)
    .detectViolations()
    .throwIfPresent();

循環依存はモジュラーモノリスで最も陥りやすい失敗です。orderinventoryが相互に依存し始めると、片方の変更がもう片方に波及し、結局分割の意味が失われます。verify()はこの相互参照を有向非巡回グラフ(DAG)として検査し、循環を許しません。

モジュール間のイベント連携(Event Publication Registry)

モジュールを疎結合に保つ推奨手段は、相手のBeanを直接呼ばずにアプリケーション内イベントで通知することです。Spring Modulithはこの用途に@ApplicationModuleListenerを用意しています。

@Component
class InventoryManagement {

  @ApplicationModuleListener
  void on(OrderCompleted event) {
    // 発行元トランザクションのコミット後に、非同期かつ新規トランザクションで実行される
  }
}

@ApplicationModuleListenerは、@Async@TransactionalEventListener@Transactional(propagation = REQUIRES_NEW)を合成したものです。つまり発注処理のトランザクションがコミットしてから、非同期・別トランザクションで在庫側が反応します。旧APIの@ApplicationEventListenerは2.0で削除されたため、現行版では使いません。

未処理イベントの永続化と再送

非同期リスナは、アプリが途中で落ちるとイベントを取りこぼす懸念があります。Spring ModulithはEvent Publication Registryで発行済みイベントをDBに記録し、リスナ成功時に完了印を付けます。永続化スターター(spring-modulith-starter-jpa等)を入れ、次の設定を有効にすると、再起動時に未完了イベントを自動で再送します。

spring.modulith.events.republish-outstanding-events-on-restart=true

各イベントはPUBLISHEDPROCESSINGCOMPLETEDと状態遷移し、失敗すればFAILEDとして残ります。この仕組みがあるため、モジュール間連携を「送りっぱなし」にせず、確実に処理される保証を付けられます。

イベントの外部化とサービス分割への布石

将来モジュールを別サービスに切り出す想定があるなら、@Externalizedでイベントをメッセージブローカーへ流せます。

@Externalized("order.completed::#{#this.orderId()}")
class OrderCompleted {
  // ルーティングキーはSpELで組み立てる(#this がイベント本体)
}

対応ブローカーはKafka・AMQP・JMS・Spring Messagingです。2.1では外部化のoutboxモード(spring.modulith.events.externalization.mode=outbox、NamastackまたはJobRunr)が加わり、DBのoutboxテーブル経由で確実に配送できるようになりました。アプリ内イベントで疎結合にしておけば、外部化に切り替えるだけでモジュールを物理的に分離できます。

モジュール単位の結合テスト

モジュールを分けたら、テストもモジュール単位で完結させたいところです。@ApplicationModuleTestは、アプリ全体ではなく対象モジュールだけをブートストラップします。

@ApplicationModuleTest(BootstrapMode.STANDALONE)
class OrderModuleTests {

  @Test
  void publishesEventOnCompletion(Scenario scenario) {
    scenario.stimulate(() -> orders.complete(orderId))
        .andWaitForEventOfType(OrderCompleted.class)
        .matching(event -> event.orderId().equals(orderId))
        .toArriveAndVerify(event -> assertThat(event).isNotNull());
  }
}

ブートストラップ範囲は3段階から選べます。STANDALONE(既定)は対象モジュールのみ、DIRECT_DEPENDENCIESは直接依存するモジュールまで、ALL_DEPENDENCIESは依存ツリー全体を起動します。範囲を絞るほど起動が速く、テストの対象も明確になります。依存モジュールのBeanは@MockitoBeanでモックに差し替えます。

例のように、テストメソッドにScenarioを注入するとイベント駆動の検証が書けます。stimulateで処理を起こし、andWaitForEventOfTypeで期待イベントの到着を非同期に待って検証するため、@ApplicationModuleListenerの非同期実行もタイミングを気にせずテストできます。

ドキュメント生成と可観測性

モジュール構造は放置すると実態と設計図が乖離します。Spring Modulithはコードから最新のモジュール図を生成でき、稼働中の構造もActuatorで確認できます。

Documenterによるモジュール図の生成

Documenterはモジュール解析結果からC4形式のPlantUML図とAsciiDocを出力します。

ApplicationModules modules = ApplicationModules.of(ShopApplication.class);
new Documenter(modules)
    .writeModulesAsPlantUml()
    .writeIndividualModulesAsPlantUml()
    .writeModuleCanvases();

出力先は既定でビルドフォルダ内のspring-modulith-docsです。図はコードを唯一の情報源として生成されるため、モジュール構成を変えれば図も追随し、ドキュメントが陳腐化しません。モジュールごとの依存・公開インターフェース・イベントを一覧するApplication Module Canvas(writeModuleCanvases)は、設計レビューの資料に向きます。

Actuatorによる稼働時の可視化

spring-modulith-starter-insightを追加すると、稼働中アプリのモジュール構造をActuatorエンドポイント/actuator/modulithがJSONで公開します。あわせてMicrometerと連携し、モジュール内Beanの呼び出しをスパンとして追跡したり、module.events.publishedなどのイベント発行数メトリクスを収集できます。どのモジュールがどれだけイベントを出しているかを運用で把握でき、ボトルネックの特定に役立ちます。

Spring Modulithを採用すべき場面と向かない場面

Spring Modulithが最も効くのは、単一チームが1つのSpring Bootアプリを育てていて、機能領域(注文・在庫・請求など)が明確に分かれる中規模以上のプロジェクトです。境界を検証で守りながら、必要になったモジュールだけを後からサービス化できます。ドメイン駆動設計(DDD)の境界づけられたコンテキストをパッケージへ落とし込む土台としても機能します。

一方、次のケースでは効果が薄いか、かえって負担になります。公式が「アンチパターン」と断言しているわけではなく、仕様から導ける実務上の判断として挙げます。

  • すでに本格的なマイクロサービス群に分割済み:Modulithの主眼はモノリス内の境界検証なので、各サービスが十分小さいなら導入の旨みが少ない。
  • パッケージを技術レイヤ別に切っているcontrollerservicerepositoryの縦割り構成は、機能単位のモジュール規約と噛み合わず、まずパッケージ再編が必要になる。ここを避けて導入しても検証が意味を持たない。
  • 小規模な単一機能アプリ:モジュール分割の必然性がない段階で構造検証やイベントレジストリを入れると、運用コストだけが増える。
  • 永続化基盤を用意できない:Event Publication Registryの再送保証はDBが前提のため、JPA/JDBC/Mongo等を準備できない環境では未完了イベントの再送が機能しない。

結論として、Spring Modulithは「分けるべき境界が既に見えているが、まだマイクロサービスの運用コストは払いたくない」タイミングで導入するのが最も費用対効果に優れます。使うアノテーションの全体像はSpring Bootの主要なアノテーション一覧とあわせて把握すると、既存プロジェクトへの組み込みがスムーズです。

よくある質問

Spring Modulithの最新バージョンは何ですか?

2026年7月時点の最新安定版は2.1.0で、2026年6月11日にGAとなりました。Spring Boot 4.1/Spring Framework 7系に対応します。Spring Boot 3.x環境では、同時にリリースされた1.4系(1.4.12)を使います。バージョンはBOM(spring-modulith-bom)で一元管理するのが安全です。

Spring Modulithを使うにはSpring Bootのどのバージョンが必要ですか?

Spring Modulith 2.x系はSpring Boot 4(2.1系は4.1)、1.4系はSpring Boot 3.3〜3.5を前提とします。プロジェクトのSpring Boot世代に合わせて選んでください。Boot 4はFramework 7ベースで、実依存としてMaven Central上でもspring-core 7.0.8が確認できます。

@ApplicationModuleListenerは通常の@EventListenerと何が違いますか?

@ApplicationModuleListener@Async@TransactionalEventListener@Transactional(REQUIRES_NEW)を合成したアノテーションで、発行元トランザクションのコミット後に非同期かつ新規トランザクションで実行されます。さらにEvent Publication Registryで発行イベントを永続化するため、アプリ再起動後に未処理イベントを再送できます。単純な同期実行の@EventListenerとは信頼性が異なります。

モジュールはどのように定義しますか?

専用の設定は不要です。@SpringBootApplicationがあるメインパッケージの直下サブパッケージが、自動的に1モジュールとして認識されます。そのbaseパッケージ直下のpublic型が公開API、internalなどの下位パッケージは非公開になります。依存範囲を絞るときだけpackage-info.java@ApplicationModule(allowedDependencies = ...)を付けます。

Spring Modulithからマイクロサービスへ移行できますか?

移行しやすくするのがSpring Modulithの狙いの1つです。モジュール間をアプリ内イベントで疎結合にしておき、@ExternalizedでイベントをKafkaやAMQPへ外部化すれば、該当モジュールを別サービスとして切り出せます。境界をverify()で守り続けていれば、分割時の依存の絡まりを最小化できます。

関連記事

資料請求

RELATED POSTS 関連記事