spring-modulith

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

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "spring-modulith" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-spring-modulith

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

Do Don't

One module = one bounded context Mix unrelated concerns

Public API in root package Expose internal classes

Implementation in internal/

Access internal from outside

Communicate via events Direct 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-Pattern Problem Solution

Circular dependency Modules reference each other Use events or shared kernel

Internal class exposed Wrong package structure Move to internal/ package

Event not published Missing transaction Verify @Transactional

Event lost No persistence Use spring-modulith-events-jpa

Callback events Events require calling back Include all data in event

Exposing repositories Tight coupling Keep repositories internal

Quick Troubleshooting

Problem Diagnostic Fix

Circular dependency Run modules.verify() Refactor to use events

Internal access violation Check package structure Move classes appropriately

Event not received Check listener Verify @EventListener annotation

Test fails in isolation Check dependencies Use appropriate BootstrapMode

Event publication fails Check transaction Ensure @Transactional present

Reference Documentation

  • Spring Modulith Reference

  • Modular Monoliths Primer

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

webrtc

No summary provided by upstream source.

Repository SourceNeeds Review