mcpField
S
spring-modulith

spring-modulith

github.com/claude-dev-suite開発4670 回インストール2 日前に更新
リポジトリ

spring-modulith について

Spring Modulith を用いたモジュラーモノリス設計を支援し、モジュール境界、公開 API、イベントベースの通信を扱うスキルです。Spring アプリケーションを境界づけられたコンテキストに整理し、アーキテクチャやモジュール間の連携を確認する開発者向けです。

主な機能

  1. 01モジュール境界と許可された依存関係
  2. 02公開 API と内部パッケージの分離
  3. 03イベントベースのモジュール間通信
  4. 04アーキテクチャ検証とモジュールテスト
  5. 05イベント永続化と再試行の考慮

活用シーン

  1. 01Spring アプリケーションのモジュラーモノリス化
  2. 02モジュール内部へのアクセス制限
  3. 03アプリケーションイベントによるモジュール連携
  4. 04モジュール境界と循環依存の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill spring-modulith

インストール後、Claude Code で/spring-modulithを入力して読み込まれたか確認します。

同梱ファイルadvanced.md

詳細

Spring Modulith

完全なリファレンス:イベントの外部化(Outbox)、モジュール API の公開、@ApplicationModuleTest、シナリオテスト、アーキテクチャ検証、可観測性、段階的な分解については、advanced.md を参照してください。

概要

┌─────────────────────────────────────────────────────────────────┐
│                      Spring Modulith Application                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐        │
│  │    Order     │   │   Payment    │   │  Inventory   │        │
│  │    Module    │──▶│    Module    │◀──│    Module    │        │
│  ├──────────────┤   ├──────────────┤   ├──────────────┤        │
│  │ order/       │   │ payment/     │   │ inventory/   │        │
│  │ ├─ api/      │   │ ├─ api/      │   │ ├─ api/      │        │
│  │ │  (public)  │   │ │  (public)  │   │ │  (public)  │        │
│  │ └─ internal/ │   │ └─ internal/ │   │ └─ internal/ │        │
│  │    (private) │   │    (private) │   │    (private) │        │
│  └──────────────┘   └──────────────┘   └──────────────┘        │
│         │                   │                   │               │
│         └───────────────────┴───────────────────┘               │
│                    Event Bus (Async)                            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

クイックスタート


<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>
src/main/java/com/example/ecommerce/
├── EcommerceApplication.java        # Root package
├── order/                           # Order module
│   ├── Order.java                   # Public API
│   ├── OrderService.java            # Public API
│   ├── OrderCreatedEvent.java       # Public event
│   └── internal/                    # Internal implementation
│       ├── OrderRepository.java
│       └── OrderValidator.java
├── payment/                         # Payment module
│   ├── PaymentService.java
│   └── internal/
└── shared/                          # Shared kernel (minimal!)
    └── Money.java

モジュール構造

// Package-info to document module
// order/package-info.java
@org.springframework.modulith.ApplicationModule(
    displayName = "Order Management",
    allowedDependencies = {"payment", "inventory::InventoryService"}
)
package com.example.ecommerce.order;
// Public API (root package)
@Service
@RequiredArgsConstructor
@Transactional
public class OrderService {

    private final OrderRepository orderRepository;
    private final ApplicationEventPublisher events;

    public Order createOrder(CreateOrderRequest request) {
        Order order = Order.create(request.customerId(), request.items());
        order = orderRepository.save(order);

        // Publish event for other modules
        events.publishEvent(new OrderCreatedEvent(order.getId(), order.getTotal()));

        return order;
    }

    public void confirmOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
        order.confirm();
        orderRepository.save(order);

        events.publishEvent(new OrderConfirmedEvent(orderId));
    }
}

// Public event
public record OrderCreatedEvent(Long orderId, Money total) {}
// Internal implementation (not accessible from other modules)
// order/internal/OrderRepository.java
@Repository
interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findByCustomerId(Long customerId);
}

イベントを介したモジュール間通信

// Payment module listens to Order module events
// payment/internal/OrderEventHandler.java
@Component
@RequiredArgsConstructor
@Slf4j
class OrderEventHandler {

    private final PaymentService paymentService;

    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        log.info("Order created: {}, processing payment", event.orderId());
        paymentService.initiatePayment(event.orderId(), event.total());
    }
}

// payment/PaymentService.java
@Service
@RequiredArgsConstructor
public class PaymentService {

    private final PaymentRepository paymentRepository;
    private final ApplicationEventPublisher events;

    public void initiatePayment(Long orderId, Money amount) {
        Payment payment = Payment.create(orderId, amount);
        payment = paymentRepository.save(payment);
        processPaymentAsync(payment);
    }

    @Async
    void processPaymentAsync(Payment payment) {
        try {
            payment.confirm();
            paymentRepository.save(payment);
            events.publishEvent(new PaymentConfirmedEvent(payment.getOrderId(), payment.getId()));
        } catch (PaymentFailedException e) {
            payment.fail(e.getMessage());
            paymentRepository.save(payment);
            events.publishEvent(new PaymentFailedEvent(payment.getOrderId(), e.getMessage()));
        }
    }
}
// Order module reacts to Payment events
// order/internal/PaymentEventHandler.java
@Component
@RequiredArgsConstructor
class PaymentEventHandler {

    private final OrderService orderService;

    @EventListener
    public void onPaymentConfirmed(PaymentConfirmedEvent event) {
        orderService.confirmOrder(event.orderId());
    }

    @EventListener
    public void onPaymentFailed(PaymentFailedEvent event) {
        orderService.cancelOrder(event.orderId(), event.reason());
    }
}

ベストプラクティス

モジュール設計
// ✅ DO: Expose only what's needed
@ApplicationModule(allowedDependencies = {"shared"})
package com.example.ecommerce.order;

// ✅ DO: Communicate via events
events.publishEvent(new OrderCreatedEvent(orderId));

// ✅ DO: Use records for immutable events
public record OrderCreatedEvent(Long orderId, Money total) {}

// ❌ DON'T: Circular dependencies
// order → payment → order  // WRONG!

// ❌ DON'T: Expose repositories
public interface OrderRepository { } // Should not be public

// ❌ DON'T: Direct access to internal
@Autowired
OrderValidator validator; // From another module - WRONG!
イベント設計
// ✅ DO: Events with all necessary data
public record OrderCreatedEvent(
    Long orderId,
    Long customerId,
    Money total,
    List<OrderItem> items,
    Instant createdAt
) {}

// ❌ DON'T: Events requiring callback
public record OrderCreatedEvent(Long orderId) {}
// Consumer must call orderService.getOrder(orderId) - WRONG!

ベストプラクティス一覧

推奨事項避けるべきこと
1 つのモジュール=1 つの境界づけられたコンテキスト無関係な関心事を混在させる
ルートパッケージに公開 API を配置する内部クラスを公開する
実装を internal/ に配置する外部から内部にアクセスする
イベントを介して通信するモジュール間で直接呼び出す
不変イベント(record)を使用する可変イベントオブジェクトを使用する

本番環境チェックリスト

  • モジュールの境界を定義する
  • 内部パッケージを適切にスコープ設定する
  • イベントベースの通信を使用する
  • アーキテクチャ検証テストを用意する
  • イベントの永続化を設定する
  • 失敗したイベントの再試行メカニズムを用意する
  • ドキュメントを生成する
  • 循環依存がない
  • 共有カーネルを最小限にする

このスキルを使用しない場合

  • シンプルなアプリケーション - 不要な複雑さが生じます
  • 既存のマイクロサービス - すでに分解されています
  • 密結合したモノリス - まず大幅なリファクタリングが必要です
  • 小規模なチーム - 厳密な境界が不要な場合があります

アンチパターン

アンチパターン問題解決策
循環依存モジュール同士が相互に参照するイベントまたは共有カーネルを使用する
内部クラスの公開パッケージ構造が不適切internal/ パッケージに移動する
イベントが公開されないトランザクションが不足している@Transactional を確認する
イベントの消失永続化されていないspring-modulith-events-jpa を使用する
コールバックイベントイベント処理にコールバックが必要必要なデータをすべてイベントに含める
リポジトリの公開密結合になるリポジトリを内部にとどめる

すぐにできるトラブルシューティング

問題診断修正
循環依存modules.verify() を実行するイベントを使用するようリファクタリングする
内部アクセス違反パッケージ構造を確認するクラスを適切な場所に移動する
イベントを受信できないリスナーを確認する@EventListener アノテーションを確認する
分離テストが失敗する依存関係を確認する適切な BootstrapMode を使用する
イベントの公開に失敗するトランザクションを確認する@Transactional が存在することを確認する

リファレンスドキュメント

コメント