spring-modulith

v2026.09.24

Spring Modulith for modular architecture in Spring Boot 3.x. Covers module structure, API vs internal packages, inter-module events, module testing, documentation generation, and observability. USE WHEN: user mentions "spring modulith", "modular monolith", "@ApplicationModule", "module boundaries", "inter-module events", "@ApplicationModuleTest", "modular architecture" DO NOT USE FOR: simple applications - unnecessary complexity, microservices - use proper service boundaries, existing tightly coupled monoliths - requires significant refactoring

GitHub
安装命令
npx skhub add claude-dev-suite/spring-modulith
Markdown
SKILL.md

Spring Modulith

Full Reference: See advanced.md for Event Externalization (Outbox), Module API Exposure, @ApplicationModuleTest, Scenario Testing, Architecture Verification, Observability, and Gradual Decomposition.

Overview

┌─────────────────────────────────────────────────────────────────┐
│                      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)                            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Quick Start

<!-- pom.xml -->
<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

Module Structure

// 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);
}

Inter-Module Communication via Events

// 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());
    }
}

Best Practices

Module Design

// ✅ 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!

Event Design

// ✅ 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!

Best Practices Table

DoDon't
One module = one bounded contextMix unrelated concerns
Public API in root packageExpose internal classes
Implementation in internal/Access internal from outside
Communicate via eventsDirect cross-module calls
Use immutable events (records)Mutable event objects

Production Checklist

  • Module boundaries defined
  • Internal packages properly scoped
  • Event-based communication
  • Architecture verification tests
  • Event persistence configured
  • Failed event retry mechanism
  • Documentation generated
  • No circular dependencies
  • Shared kernel minimal

When NOT to Use This Skill

  • Simple applications - Unnecessary complexity
  • Existing microservices - Already decomposed
  • Tightly coupled monoliths - Requires significant refactoring first
  • Small teams - May not need formal boundaries

Anti-Patterns

Anti-PatternProblemSolution
Circular dependencyModules reference each otherUse events or shared kernel
Internal class exposedWrong package structureMove to internal/ package
Event not publishedMissing transactionVerify @Transactional
Event lostNo persistenceUse spring-modulith-events-jpa
Callback eventsEvents require calling backInclude all data in event
Exposing repositoriesTight couplingKeep repositories internal

Quick Troubleshooting

ProblemDiagnosticFix
Circular dependencyRun modules.verify()Refactor to use events
Internal access violationCheck package structureMove classes appropriately
Event not receivedCheck listenerVerify @EventListener annotation
Test fails in isolationCheck dependenciesUse appropriate BootstrapMode
Event publication failsCheck transactionEnsure @Transactional present

Reference Documentation

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

skills/backend-frameworks/spring-modulith

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1