Spring Modulith

https://github.com/spring-boot-tutorials/spring-modulith

In this article we will configure Spring Modulith.

Create Initial Code Base

  • Go to https://start.spring.io/

  • Add the following dependencies:

    • spring-modulith-starter-core

    • spring-boot-starter-web

    • lombok

  • Click Generate

Dependencies

Dependencies used in pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.modulith</groupId>
    <artifactId>spring-modulith-starter-core</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

Modulith #1 - POJO

Let’s create a new POJO src/main/java/com/example/spring_modulith/module_notification/NotificationDTO.java

@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class NotificationDTO {
    private String productName;
    private String format;
    private Date date;
}

Modulith #1 - Service

Create a new file src/main/java/com/example/spring_modulith/module_notification/NotificationService.java:

@Service
public class NotificationService {

    private static final Logger LOG = LoggerFactory.getLogger(NotificationService.class);

    public void createNotification(NotificationDTO notification) {
        LOG.info("Received notification by module DEPENDENCY for product {} in date {} by {}.",
                notification.getProductName(),
                notification.getDate(),
                notification.getFormat());
    }

    // TODO doesn't work
    @ApplicationModuleListener
    public void notificationEvent(NotificationDTO event) {
        LOG.info("Received notification by EVENT for product {} in date {} by {}.",
                event.getProductName(),
                event.getDate(),
                event.getFormat());
    }
}

Modulith #1 - Internal - POJO

Create a new POJO class src/main/java/com/example/spring_modulith/module_notification/internal/Notification.java:

@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class Notification {
    private String productName;
    private NotificationType format;
    private Date date;
}

And another POJO class src/main/java/com/example/spring_modulith/module_notification/internal/NotificationType.java:

public enum NotificationType {
    SMS
}

Modulith #2 - Service

Create new file src/main/java/com/example/spring_modulith/module_product/ProductService.java:

@Service
@RequiredArgsConstructor
public class ProductService {

    private final ApplicationEventPublisher events;
    private final NotificationService notificationService;

    public void create(Product product) {
        notificationService.createNotification(new NotificationDTO(product.getName(), "SMS", new Date()));
        events.publishEvent(new NotificationDTO(product.getName(), "SMS", new Date()));
    }
}

Modulith #2 - Internal - POJO

Create new POJO class src/main/java/com/example/spring_modulith/module_product/internal/Product.java:

@Data
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
public class Product {
    private String name;
    private String description;
    private int price;
}

Main

Modify MainApplication.java:

@EnableAsync
@SpringBootApplication
public class SpringModulithApplication {

    public static void main(String[] args) {
            SpringApplication.run(SpringModulithApplication.class, args)
                            .getBean(ProductService.class)
                            .create(new Product("marcus", "description", 100));
    }
}

Create Tests

Create new file src/test/java/com/example/spring_modulith/SpringModulithApplicationTests.java:

@SpringBootTest
class SpringModulithApplicationTests {

    @Test
    void createApplicationModuleModel() {
            ApplicationModules modules = ApplicationModules.of(SpringModulithApplication.class);
            modules.forEach(System.out::println);
    }

    @Test
    void verifiesModularStructure() {
            ApplicationModules modules = ApplicationModules.of(SpringModulithApplication.class);
            modules.verify();
    }

    @Test
    void createModuleDocumentation() {
            ApplicationModules modules = ApplicationModules.of(SpringModulithApplication.class);
            new Documenter(modules)
                            .writeDocumentation()
                            .writeIndividualModulesAsPlantUml();
    }
}

Run & Verify Tests

Open terminal at project root and execute the following:

mvn clean package