Introduction
In this article, I would like to introduce to Spring Modulith. And I will resolve some questions such as What is it? Why I have to use?. The Idea of the monolith start with:
Martin Fowler (2015) - “Monolith First”:
“you shouldn’t start a new project with microservices, even if you’re sure your application will be big enough to make it worthwhile. .” [ˆ1]
Sam Newman - “Building Microservices”:
“Start with a monolith, extract microservices”
Also I show a simple demo how to start.
1. What is Spring Modulith
Spring Modulith is a library that allows create modular monoliths. Where each module is a domain of the application. Internally the monolithic system is structured to be easy to maintain, test and if required allows division into microservices when necessary.
Spring Modulith offers the next features
Features
- Encourage to create modules with bounded context
- Supports event-driven communication (sync and async)
- Generation of documentation as UML diagrams like components
- Transactional Events: Spring Modulith introduces a way of publish events only when the transaction has been completed successfully. Each event is registered in the DB allowing subsequent retries if it fails.
- Architectural Validation
2. Why I should use Spring Modulith
Primarily because it creates a modular monolith with a well-defined structure that can be easily decomposed into microservices when needed.
3. Annotations more used
Spring Modulith provides usa package of annotations such as:
@ApplicationModule
- Marks a package/class as an application module.
- Defines explicit module boundaries.
@ApplicationModuleListener
- Declares an internal event listener between modules.
- Similar to Spring’s @EventListener, but focused on intra-module communication.
@NamedInterface
- Defines an interface within a module that can be accessed externally.
- Useful for controlling what is exposed and what remains internal.
@ApplicationModuleTest
- For testing focused on a specific module, isolating its dependencies.
4. Demo
In this example we work with two modules Order and Inventory:
- When an order is created, the Inventory module should update the stock
- The communication between both modules is based on Events
- Each module has Internal classes and some exposed classes
link Spring Initializr
We have the following project structure for the Order module. This structure use the default package/module configuration, where each sub-package under the main package is considered an application module.
1└── src
2 ├── main
3 │ ├── java
4 │ │ └── dev
5 │ │ └── davidsarmiento
6 │ │ └── modulith
7 │ │ ├── ExampleSpringModulithApplication.java
8 │ │ └── order
9 │ │ ├── controller
10 │ │ │ └── OrderController.java
11 │ │ ├── dto
12 │ │ │ ├── CreateOrderRequest.java
13 │ │ │ ├── OrderItemDTO.java
14 │ │ │ └── package-info.java
15 │ │ ├── event
16 │ │ │ ├── OrderCreatedEvent.java
17 │ │ │ └── package-info.java
18 │ │ ├── model
19 │ │ │ ├── Order.java
20 │ │ │ ├── OrderItem.java
21 │ │ │ └── OrderStatus.java
22 │ │ ├── repository
23 │ │ │ ├── OrderItemRepository.java
24 │ │ │ └── OrderRepository.java
25 │ │ └── service
26 │ │ ├── OrderService.java
27 │ │ └── OrderServiceImpl.java
The most relevant part of the module is:
- The use of some annotations such as in package dto package-info.java :
1@org.springframework.modulith.NamedInterface("dto")
2package dev.davidsarmiento.modulith.order.dto;
in package event -> package-info.java:
1@org.springframework.modulith.NamedInterface("event")
2package dev.davidsarmiento.modulith.order.event;
In both cases we use NamedInterface to expose that package classes to other modules, in this case we will share these to Inventory module.
- Publishing of Events used in createOrder() method.
1public class OrderServiceImpl implements OrderService {
2
3 private final OrderRepository orderRepository;
4 private final ApplicationEventPublisher eventPublisher;
5
6@Override
7@Transactional
8public Order createOrder(CreateOrderRequest request) {
9 Order order = new Order();
10 order.setOrderDate(LocalDateTime.now());
11 order.setStatus(OrderStatus.COMPLETED);
12
13 List<OrderItem> orderItems = request.items().stream()
14 .map(itemDTO -> toOrderItem(itemDTO, order))
15 .collect(Collectors.toList());
16 order.setItems(orderItems);
17
18 Order savedOrder = orderRepository.save(order);
19
20 eventPublisher.publishEvent(new OrderCreatedEvent(this, request.items()));
21
22 return savedOrder;
23}
We use an instance of ApplicationEventPublisher to publish a Event for our following module called Inventory.
Inventory module has the responsibility of keep updated the stock of Product.
1└── src
2 ├── main
3 │ ├── java
4 │ │ └── dev
5 │ │ └── davidsarmiento
6 │ │ └── modulith
7 │ │ ├── ExampleSpringModulithApplication.java
8 │ │ ├── inventory
9 │ │ │ ├── controller
10 │ │ │ │ └── InventoryController.java
11 │ │ │ ├── dto
12 │ │ │ │ └── ProductInventoryDTO.java
13 │ │ │ ├── event
14 │ │ │ │ └── OrderEventListener.java
15 │ │ │ ├── model
16 │ │ │ │ └── Product.java
17 │ │ │ ├── package-info.java
18 │ │ │ ├── repository
19 │ │ │ │ └── ProductRepository.java
20 │ │ │ └── service
21 │ │ │ ├── InventoryService.java
22 │ │ │ └── InventoryServiceImpl.java
The most relevant part of the Inventory module is:
- Use of @EventListener annotation to process the OrderCreatedEvent published by Order module
1public class OrderEventListener {
2
3 private final InventoryService inventoryService;
4
5 @EventListener
6 public void handleOrderCreatedEvent(OrderCreatedEvent event) {
7 int retries = 3;
8 while (retries > 0) {
9 try {
10 inventoryService.updateInventory(event.getItems());
11 return; // Success
12 } catch (ObjectOptimisticLockingFailureException e) {
13 retries--;
14 if (retries == 0) {
15 throw e;
16 }
17 System.out.println("Retrying inventory update due to optimistic locking failure...");
18 }
19 }
20 }
21}
- The ApplicactionModule annotation defined in inventory/package-info.java, used to defined the allowedDependencies from Order module.
1@org.springframework.modulith.ApplicationModule(allowedDependencies = {"order::dto", "order::event"})
2package dev.davidsarmiento.modulith.inventory;
- Verification of the valid module structures we can use this provided method verify():
1@Test
2void verifyModules() {
3 ApplicationModules.of(ExampleSpringModulithApplication.class).verify();
4}
- Documentation
- Spring Modulith has some tools to generate documentation in format uml and C4 , by the diagrama default folder is: build/spring-modulith-docs
Tip: To be able to see this diagram you have to install PlantUML plugin for IntelliJ
1public class DocumentationTests {
2
3 ApplicationModules modules = ApplicationModules.of(ExampleSpringModulithApplication.class);
4
5 @Test
6 void writeDocumentationSnippets() {
7
8 new Documenter(modules)
9 .writeModulesAsPlantUml()
10 .writeIndividualModulesAsPlantUml();
11 }
12}
Component Diagram Generated by Spring Modulith
5. Conclusion
- Spring Modulith is more than a library, it is a framework.
- In this demo, we were introduced to basic use of modules with Spring Modulith. We also understand the use of some annotations :
@ApplicationModuley@NamedInterface - Spring Modulith promotes the use of asynchronous event communication among modules.
- Spring Modulith provides a series of tools to test and document our modular application.
Source Code
References
[ˆ1] https://martinfowler.com/bliki/MonolithFirst.html https://docs.spring.io/spring-modulith/reference/fundamentals.html