Skip to content

Commit 86ec612

Browse files
committed
Introduce Spring Modulith
1 parent e316074 commit 86ec612

File tree

83 files changed

+1256
-253
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+1256
-253
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44

55
/gradlew text eol=lf
66
*.bat text eol=crlf
7+
8+
*.css linguist-generated=true

README.md

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,53 @@
1-
# Spring PetClinic Sample Application [![Build Status](https://github.com/spring-projects/spring-petclinic/actions/workflows/maven-build.yml/badge.svg)](https://github.com/spring-projects/spring-petclinic/actions/workflows/maven-build.yml)[![Build Status](https://github.com/spring-projects/spring-petclinic/actions/workflows/gradle-build.yml/badge.svg)](https://github.com/spring-projects/spring-petclinic/actions/workflows/gradle-build.yml)
1+
# Spring PetClinic Sample Application [![Build Status](https://github.com/spring-petclinic/spring-petclinic-modulith/actions/workflows/maven-build.yml/badge.svg)](https://github.com/spring-petclinic/spring-petclinic-modulith/actions/workflows/maven-build.yml)[![Build Status](https://github.com//spring-petclinic/spring-petclinic-modulith/actions/workflows/gradle-build.yml/badge.svg)](https://github.com/spring-petclinic/spring-petclinic-modulith/actions/workflows/gradle-build.yml)
2+
3+
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/spring-petclinic/spring-petclinic-modulith) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=7517918)
4+
5+
This fork of Spring PetClinic demonstrates how to structure a Spring Boot application as a set of well-defined, loosely coupled modules using **[Spring Modulith](https://docs.spring.io/spring-modulith/reference/)**.
6+
7+
## Spring Modulith Architecture
8+
9+
The application is organized into **three application modules**, each owning its domain logic, persistence layer, and web controllers:
10+
11+
```
12+
org.springframework.samples.petclinic/
13+
├── owner/ ← Public API (VisitBooked event record)
14+
│ ├── application/ ← Services (VisitScheduler) (private)
15+
│ ├── domain/ ← Entities and repositories (private)
16+
│ └── ui/ ← Controllers (private)
17+
├── vet/ ← Public API (declared dependency on owner)
18+
│ └── internal/ ← Controllers, services, repositories, entities (private)
19+
└── system/ ← Cross-cutting: cache, i18n, error handling (no dependencies)
20+
└── internal/
21+
```
22+
23+
### Module interaction
24+
25+
Modules communicate exclusively through **application events** — no direct Spring bean injection across module boundaries:
26+
27+
```
28+
owner module vet module
29+
───────────────── ──────────────────────────────
30+
VisitController VetEventListener
31+
└─ VisitScheduler @ApplicationModuleListener
32+
└─ events.publishEvent( └─ VetRoster
33+
VisitBooked(...)) └─ assigns least-loaded vet
34+
└─ persists in visit_assignments
35+
```
36+
37+
The `vet` module also listens to `DayHasPassed` (Spring Modulith Moments) to clean up past assignments daily.
38+
39+
### Key Spring Modulith features demonstrated
40+
41+
| Feature | Implementation |
42+
|------------------------------------------------------------|------------------------------------------------------------|
43+
| `@Modulithic` + `@ApplicationModule` | Structural verification, explicit dependency declarations |
44+
| `ApplicationEventPublisher` + `@ApplicationModuleListener` | Async transactional cross-module events |
45+
| Event Publication Registry (JDBC) | At-least-once delivery, `event_publication` table |
46+
| Moments — `DayHasPassed` | Passage-of-time events, daily cleanup scheduler |
47+
| `@ApplicationModuleTest` + `Scenario` | Isolated module integration tests |
48+
| `Documenter` | C4 diagrams and AsciiDoc in `target/spring-modulith-docs/` |
49+
| `/actuator/modulith` | Runtime module graph endpoint |
250

3-
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/spring-projects/spring-petclinic) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=7517918)
451

552
## Understanding the Spring Petclinic application with a few diagrams
653

@@ -10,6 +57,7 @@ See the presentation here:
1057
> **Note:** These slides refer to a legacy, pre–Spring Boot version of Petclinic and may not reflect the current Spring Boot–based implementation.
1158
> For up-to-date information, please refer to this repository and its documentation.
1259
60+
Module architecture documentation (C4 diagrams, component diagrams) is automatically generated by the `Documenter` test into the `target/spring-modulith-docs/` directory.
1361

1462
## Run Petclinic locally
1563

@@ -19,8 +67,8 @@ Java 17 or later is required for the build, and the application can run with Jav
1967
You first need to clone the project locally:
2068

2169
```bash
22-
git clone https://github.com/spring-projects/spring-petclinic.git
23-
cd spring-petclinic
70+
git clone https://github.com/spring-petclinic/spring-petclinic-modulith.git
71+
cd spring-petclinic-modulith
2472
```
2573
If you are using Maven, you can start the application on the command-line as follows:
2674

@@ -140,9 +188,22 @@ The following items should be installed in your system:
140188

141189
|Spring Boot Configuration | Class or Java property files |
142190
|--------------------------|---|
143-
|The Main Class | [PetClinicApplication](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java) |
144-
|Properties Files | [application.properties](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/resources) |
145-
|Caching | [CacheConfiguration](https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java) |
191+
|The Main Class | [PetClinicApplication](src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java) |
192+
|Properties Files | [application.properties](src/main/resources/application.properties) |
193+
|Caching | [CacheConfiguration](src/main/java/org/springframework/samples/petclinic/system/internal/CacheConfiguration.java) |
194+
195+
| Spring Modulith | Class or file |
196+
|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
197+
| Module declarations | [owner/package-info.java](src/main/java/org/springframework/samples/petclinic/owner/package-info.java), [vet/package-info.java](src/main/java/org/springframework/samples/petclinic/vet/package-info.java), [system/package-info.java](src/main/java/org/springframework/samples/petclinic/system/package-info.java) |
198+
| Cross-module event | [VisitBooked](src/main/java/org/springframework/samples/petclinic/owner/VisitBooked.java) |
199+
| Event publisher | [VisitScheduler](src/main/java/org/springframework/samples/petclinic/owner/application/VisitScheduler.java) |
200+
| Event listener | [VetEventListener](src/main/java/org/springframework/samples/petclinic/vet/internal/VetEventListener.java) |
201+
| Vet assignment logic | [VetRoster](src/main/java/org/springframework/samples/petclinic/vet/internal/VetRoster.java) |
202+
| Modularity verification | [ModularityTests](src/test/java/org/springframework/samples/petclinic/ModularityTests.java) |
203+
| Module test — owner | [VisitSchedulerTests](src/test/java/org/springframework/samples/petclinic/owner/application/VisitSchedulerTests.java) |
204+
| Module test — vet | [VetAssignmentTests](src/test/java/org/springframework/samples/petclinic/vet/internal/VetAssignmentTests.java) |
205+
| Generated architecture docs | `target/spring-modulith-docs/` (run `ModularityTests`) |
206+
| Modulith actuator endpoint | `GET http://localhost:8080/actuator/modulith` |
146207

147208
## Interesting Spring Petclinic branches and forks
148209

build.gradle

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ ext.springJavaformatCheckstyleVersion = "0.0.47"
2929
ext.webjarsLocatorLiteVersion = "1.1.2"
3030
ext.webjarsFontawesomeVersion = "4.7.0"
3131
ext.webjarsBootstrapVersion = "5.3.8"
32+
ext.springModulithVersion = "2.0.5"
33+
34+
dependencyManagement {
35+
imports {
36+
mavenBom "org.springframework.modulith:spring-modulith-bom:${springModulithVersion}"
37+
}
38+
}
3239

3340
dependencies {
3441
implementation 'org.springframework.boot:spring-boot-starter-cache'
@@ -38,14 +45,20 @@ dependencies {
3845
implementation 'org.springframework.boot:spring-boot-starter-validation'
3946
implementation 'javax.cache:cache-api'
4047
implementation 'jakarta.xml.bind:jakarta.xml.bind-api'
41-
runtimeOnly 'org.springframework.boot:spring-boot-starter-actuator'
48+
implementation 'org.springframework.modulith:spring-modulith-api'
49+
implementation 'org.springframework.modulith:spring-modulith-events-api'
50+
implementation 'org.springframework.modulith:spring-modulith-moments'
51+
implementation 'org.springframework.modulith:spring-modulith-starter-jdbc'
52+
implementation 'org.springframework.boot:spring-boot-starter-actuator'
4253
runtimeOnly "org.webjars:webjars-locator-lite:${webjarsLocatorLiteVersion}"
4354
runtimeOnly "org.webjars.npm:bootstrap:${webjarsBootstrapVersion}"
4455
runtimeOnly "org.webjars.npm:font-awesome:${webjarsFontawesomeVersion}"
4556
runtimeOnly 'com.github.ben-manes.caffeine:caffeine'
4657
runtimeOnly 'com.h2database:h2'
4758
runtimeOnly 'com.mysql:mysql-connector-j'
4859
runtimeOnly 'org.postgresql:postgresql'
60+
runtimeOnly 'org.springframework.modulith:spring-modulith-actuator'
61+
runtimeOnly 'org.springframework.modulith:spring-modulith-runtime'
4962
developmentOnly 'org.springframework.boot:spring-boot-devtools'
5063
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
5164
testImplementation 'org.springframework.boot:spring-boot-starter-restclient-test'
@@ -54,6 +67,9 @@ dependencies {
5467
testImplementation 'org.springframework.boot:spring-boot-docker-compose'
5568
testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
5669
testImplementation 'org.testcontainers:testcontainers-mysql'
70+
testImplementation 'org.springframework.modulith:spring-modulith-junit'
71+
testImplementation 'org.springframework.modulith:spring-modulith-docs'
72+
testImplementation 'org.springframework.modulith:spring-modulith-starter-test'
5773
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${springJavaformatCheckstyleVersion}"
5874
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
5975
}

pom.xml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@
1919
<!-- Important for reproducible builds. Update using e.g. ./mvnw versions:set -DnewVersion=... -->
2020
<project.build.outputTimestamp>2024-11-28T14:37:52Z</project.build.outputTimestamp>
2121

22+
<!-- Spring Modulith -->
23+
<spring-modulith.version>2.0.5</spring-modulith.version>
24+
2225
<!-- Web dependencies -->
2326
<webjars-locator.version>1.1.2</webjars-locator.version>
2427
<webjars-bootstrap.version>5.3.8</webjars-bootstrap.version>
2528
<webjars-font-awesome.version>4.7.0</webjars-font-awesome.version>
2629

30+
<!-- Maven plugins -->
2731
<checkstyle.version>12.1.2</checkstyle.version>
2832
<jacoco.version>0.8.14</jacoco.version>
2933
<libsass.version>0.3.4</libsass.version>
@@ -67,6 +71,50 @@
6771
<artifactId>spring-boot-starter-webmvc</artifactId>
6872
</dependency>
6973

74+
75+
<!-- Spring Modulith dependencies -->
76+
<dependency>
77+
<groupId>org.springframework.modulith</groupId>
78+
<artifactId>spring-modulith-api</artifactId>
79+
</dependency>
80+
<dependency>
81+
<groupId>org.springframework.modulith</groupId>
82+
<artifactId>spring-modulith-junit</artifactId>
83+
<scope>test</scope>
84+
</dependency>
85+
<dependency>
86+
<groupId>org.springframework.modulith</groupId>
87+
<artifactId>spring-modulith-docs</artifactId>
88+
<scope>test</scope>
89+
</dependency>
90+
<dependency>
91+
<groupId>org.springframework.modulith</groupId>
92+
<artifactId>spring-modulith-starter-test</artifactId>
93+
<scope>test</scope>
94+
</dependency>
95+
<dependency>
96+
<groupId>org.springframework.modulith</groupId>
97+
<artifactId>spring-modulith-events-api</artifactId>
98+
</dependency>
99+
<dependency>
100+
<groupId>org.springframework.modulith</groupId>
101+
<artifactId>spring-modulith-moments</artifactId>
102+
</dependency>
103+
<dependency>
104+
<groupId>org.springframework.modulith</groupId>
105+
<artifactId>spring-modulith-starter-jdbc</artifactId>
106+
</dependency>
107+
<dependency>
108+
<groupId>org.springframework.modulith</groupId>
109+
<artifactId>spring-modulith-actuator</artifactId>
110+
<scope>runtime</scope>
111+
</dependency>
112+
<dependency>
113+
<groupId>org.springframework.modulith</groupId>
114+
<artifactId>spring-modulith-runtime</artifactId>
115+
<scope>runtime</scope>
116+
</dependency>
117+
70118
<dependency>
71119
<groupId>javax.cache</groupId>
72120
<artifactId>cache-api</artifactId>
@@ -290,6 +338,19 @@
290338
</plugin>
291339
</plugins>
292340
</build>
341+
342+
<dependencyManagement>
343+
<dependencies>
344+
<dependency>
345+
<groupId>org.springframework.modulith</groupId>
346+
<artifactId>spring-modulith-bom</artifactId>
347+
<version>${spring-modulith.version}</version>
348+
<type>pom</type>
349+
<scope>import</scope>
350+
</dependency>
351+
</dependencies>
352+
</dependencyManagement>
353+
293354
<profiles>
294355
<profile>
295356
<id>css</id>

src/main/java/org/springframework/samples/petclinic/PetClinicApplication.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@
1818

1919
import org.springframework.boot.SpringApplication;
2020
import org.springframework.boot.autoconfigure.SpringBootApplication;
21-
import org.springframework.context.annotation.ImportRuntimeHints;
21+
import org.springframework.modulith.Modulithic;
2222

2323
/**
2424
* PetClinic Spring Boot Application.
2525
*
2626
* @author Dave Syer
2727
*/
28+
@Modulithic(systemName = "PetClinic")
2829
@SpringBootApplication
29-
@ImportRuntimeHints(PetClinicRuntimeHints.class)
3030
public class PetClinicApplication {
3131

3232
public static void main(String[] args) {

src/main/java/org/springframework/samples/petclinic/model/Person.java

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/main/java/org/springframework/samples/petclinic/owner/PetType.java renamed to src/main/java/org/springframework/samples/petclinic/owner/VisitBooked.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2025 the original author or authors.
2+
* Copyright 2012-2026 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,16 +15,12 @@
1515
*/
1616
package org.springframework.samples.petclinic.owner;
1717

18-
import org.springframework.samples.petclinic.model.NamedEntity;
19-
20-
import jakarta.persistence.Entity;
21-
import jakarta.persistence.Table;
18+
import java.time.LocalDate;
2219

2320
/**
24-
* @author Juergen Hoeller Can be Cat, Dog, Hamster...
21+
* Event published by the {@code owner} module when a visit is booked. This is part of the
22+
* module's public API so other modules can react to it without depending on {@code owner}
23+
* internal classes.
2524
*/
26-
@Entity
27-
@Table(name = "types")
28-
public class PetType extends NamedEntity {
29-
25+
public record VisitBooked(int visitId, int petId, LocalDate date) {
3026
}

0 commit comments

Comments
 (0)