spring-boot4-skill 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -0
- package/bin/create-spring-app.js +48 -0
- package/lib/generator.js +188 -0
- package/lib/prompts.js +115 -0
- package/lib/templates.js +45 -0
- package/package.json +44 -0
- package/skills/java-spring-framework/SKILL.md +56 -0
- package/skills/java-spring-framework/references/build-templates.md +304 -0
- package/skills/java-spring-framework/references/spring-boot-4.md +347 -0
- package/skills/java-spring-framework/references/spring-framework-7.md +314 -0
- package/skills/java-spring-framework/references/spring-modulith.md +220 -0
- package/skills-lock.json +20 -0
- package/templates/gradle-kotlin/build.gradle.kts.template +113 -0
- package/templates/gradle-kotlin/compose.yaml.template +40 -0
- package/templates/gradle-kotlin/settings.gradle.kts.template +1 -0
- package/templates/gradle-kotlin/src/main/java/com/example/app/Application.java.template +19 -0
- package/templates/gradle-kotlin/src/main/resources/application.yaml.template +60 -0
- package/templates/gradle-kotlin/src/test/java/com/example/app/ApplicationTests.java.template +17 -0
- package/templates/maven/pom.xml.template +159 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Spring Framework 7.0.x — Feature Reference
|
|
2
|
+
|
|
3
|
+
**Version**: 7.0.5 (Stable) | **Java baseline**: 17 (25 recommended) | **Jakarta EE**: 11
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
1. [API Versioning](#1-api-versioning)
|
|
9
|
+
2. [Null Safety — JSpecify](#2-null-safety--jspecify)
|
|
10
|
+
3. [Resilience Annotations](#3-resilience-annotations)
|
|
11
|
+
4. [Programmatic Bean Registration](#4-programmatic-bean-registration)
|
|
12
|
+
5. [RestClient (Synchronous)](#5-restclient-synchronous)
|
|
13
|
+
6. [HTTP Service Proxy (Declarative)](#6-http-service-proxy-declarative)
|
|
14
|
+
7. [JdbcClient](#7-jdbcclient)
|
|
15
|
+
8. [JmsClient](#8-jmsclient)
|
|
16
|
+
9. [Streaming Support](#9-streaming-support)
|
|
17
|
+
10. [SpEL — Optional Support](#10-spel--optional-support)
|
|
18
|
+
11. [Enhanced PathPattern](#11-enhanced-pathpattern)
|
|
19
|
+
12. [RestTestClient (Testing)](#12-resttestclient-testing)
|
|
20
|
+
13. [Removed APIs Migration](#13-removed-apis-migration)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. API Versioning
|
|
25
|
+
|
|
26
|
+
Spring 7 introduces **native HTTP API versioning** via `@RequestMapping(version = "...")`.
|
|
27
|
+
Routing uses the `Version` HTTP request header by default.
|
|
28
|
+
|
|
29
|
+
```java
|
|
30
|
+
@RestController
|
|
31
|
+
@RequestMapping("/api/products")
|
|
32
|
+
public class ProductController {
|
|
33
|
+
|
|
34
|
+
@GetMapping(value = "/search", version = "1")
|
|
35
|
+
public List<ProductV1> searchV1(@RequestParam String query) { ... }
|
|
36
|
+
|
|
37
|
+
@GetMapping(value = "/search", version = "2")
|
|
38
|
+
public ProductPageV2 searchV2(@RequestParam String query,
|
|
39
|
+
@RequestParam(defaultValue = "10") int limit) { ... }
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**No library required** — built into Spring MVC and WebFlux.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 2. Null Safety — JSpecify
|
|
48
|
+
|
|
49
|
+
Spring 7 adopts **JSpecify** (`org.jspecify`) for null safety annotations.
|
|
50
|
+
|
|
51
|
+
```java
|
|
52
|
+
import org.jspecify.annotations.NonNull;
|
|
53
|
+
import org.jspecify.annotations.Nullable;
|
|
54
|
+
import org.jspecify.annotations.NullMarked;
|
|
55
|
+
|
|
56
|
+
@NullMarked // entire package is non-null by default
|
|
57
|
+
@Service
|
|
58
|
+
public class UserService {
|
|
59
|
+
|
|
60
|
+
public @NonNull User findRequired(@NonNull String userId) { ... }
|
|
61
|
+
|
|
62
|
+
public @Nullable User findOptional(@Nullable String userId) { ... }
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Gradle dependency**:
|
|
67
|
+
```kotlin
|
|
68
|
+
implementation("org.jspecify:jspecify:1.0.0")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 3. Resilience Annotations
|
|
74
|
+
|
|
75
|
+
Spring 7 ships **built-in resilience** — no Resilience4j required for basic cases.
|
|
76
|
+
|
|
77
|
+
```java
|
|
78
|
+
@Service
|
|
79
|
+
@EnableResilientMethods
|
|
80
|
+
public class PaymentService {
|
|
81
|
+
|
|
82
|
+
@Retryable(maxAttempts = 3, delay = 500)
|
|
83
|
+
public Receipt processPayment(PaymentRequest req) { ... }
|
|
84
|
+
|
|
85
|
+
@ConcurrencyLimit(maxConcurrentCalls = 10)
|
|
86
|
+
public Balance fetchBalance(String accountId) { ... }
|
|
87
|
+
|
|
88
|
+
@CircuitBreaker(failureRateThreshold = 0.5)
|
|
89
|
+
public ExchangeRate getExchangeRate(String currency) { ... }
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 4. Programmatic Bean Registration
|
|
96
|
+
|
|
97
|
+
Prefer `BeanRegistrar` over `@Bean`-heavy classes. Avoids proxy overhead and is GraalVM-friendly.
|
|
98
|
+
|
|
99
|
+
```java
|
|
100
|
+
@Configuration(proxyBeanMethods = false)
|
|
101
|
+
public class InfrastructureConfig implements BeanRegistrar {
|
|
102
|
+
|
|
103
|
+
@Override
|
|
104
|
+
public void registerBeans(BeanRegistry registry) {
|
|
105
|
+
registry.registerBean("userRepository",
|
|
106
|
+
UserRepository.class,
|
|
107
|
+
spec -> spec.supplier(UserRepositoryImpl::new)
|
|
108
|
+
.scope(BeanDefinition.SCOPE_SINGLETON));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 5. RestClient (Synchronous)
|
|
116
|
+
|
|
117
|
+
`RestClient` replaces `RestTemplate`. Fluent, immutable, interceptor-aware.
|
|
118
|
+
|
|
119
|
+
```java
|
|
120
|
+
@Configuration(proxyBeanMethods = false)
|
|
121
|
+
public class HttpConfig {
|
|
122
|
+
|
|
123
|
+
@Bean
|
|
124
|
+
RestClient restClient(RestClient.Builder builder) {
|
|
125
|
+
return builder
|
|
126
|
+
.baseUrl("https://api.example.com")
|
|
127
|
+
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
|
128
|
+
.build();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Usage
|
|
133
|
+
record Product(Long id, String name) {}
|
|
134
|
+
|
|
135
|
+
List<Product> products = restClient.get()
|
|
136
|
+
.uri("/products")
|
|
137
|
+
.retrieve()
|
|
138
|
+
.body(new ParameterizedTypeReference<List<Product>>() {});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 6. HTTP Service Proxy (Declarative)
|
|
144
|
+
|
|
145
|
+
Use `@HttpExchange` interfaces with `HttpServiceProxyFactory` for zero-boilerplate clients.
|
|
146
|
+
|
|
147
|
+
```java
|
|
148
|
+
// Declare the interface
|
|
149
|
+
@HttpExchange("/products")
|
|
150
|
+
public interface ProductClient {
|
|
151
|
+
|
|
152
|
+
@GetExchange("/{id}")
|
|
153
|
+
Product findById(@PathVariable Long id);
|
|
154
|
+
|
|
155
|
+
@PostExchange
|
|
156
|
+
Product create(@RequestBody Product product);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Register as a bean
|
|
160
|
+
@Configuration(proxyBeanMethods = false)
|
|
161
|
+
@ImportHttpServices(group = "product", types = {ProductClient.class})
|
|
162
|
+
public class ClientConfig {}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Auto-configuration wires the `RestClient` adapter. For custom setup:
|
|
166
|
+
|
|
167
|
+
```java
|
|
168
|
+
@Bean
|
|
169
|
+
ProductClient productClient(RestClient restClient) {
|
|
170
|
+
return HttpServiceProxyFactory
|
|
171
|
+
.builderFor(RestClientAdapter.create(restClient))
|
|
172
|
+
.build()
|
|
173
|
+
.createClient(ProductClient.class);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 7. JdbcClient
|
|
180
|
+
|
|
181
|
+
Fluent JDBC wrapper — replaces direct `JdbcTemplate` usage.
|
|
182
|
+
|
|
183
|
+
```java
|
|
184
|
+
@Repository
|
|
185
|
+
public class UserRepository {
|
|
186
|
+
|
|
187
|
+
private final JdbcClient jdbc;
|
|
188
|
+
|
|
189
|
+
public UserRepository(JdbcClient jdbc) {
|
|
190
|
+
this.jdbc = jdbc;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public List<User> findAll() {
|
|
194
|
+
return jdbc.sql("SELECT id, name, email FROM users")
|
|
195
|
+
.query(User.class) // uses RowMapper by convention
|
|
196
|
+
.list();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public Optional<User> findById(Long id) {
|
|
200
|
+
return jdbc.sql("SELECT id, name, email FROM users WHERE id = :id")
|
|
201
|
+
.param("id", id)
|
|
202
|
+
.query(User.class)
|
|
203
|
+
.optional();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public int save(User user) {
|
|
207
|
+
return jdbc.sql("INSERT INTO users (name, email) VALUES (:name, :email)")
|
|
208
|
+
.param("name", user.name())
|
|
209
|
+
.param("email", user.email())
|
|
210
|
+
.update();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 8. JmsClient
|
|
218
|
+
|
|
219
|
+
Fluent JMS API replacing `JmsTemplate`.
|
|
220
|
+
|
|
221
|
+
```java
|
|
222
|
+
@Service
|
|
223
|
+
public class OrderEventPublisher {
|
|
224
|
+
|
|
225
|
+
private final JmsClient jms;
|
|
226
|
+
|
|
227
|
+
public OrderEventPublisher(JmsClient jms) {
|
|
228
|
+
this.jms = jms;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public void publishOrder(OrderCreatedEvent event) {
|
|
232
|
+
jms.send("orders.created", event);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public OrderCreatedEvent consumeNext() {
|
|
236
|
+
return jms.receive("orders.created", OrderCreatedEvent.class);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 9. Streaming Support
|
|
244
|
+
|
|
245
|
+
Spring MVC now accepts `InputStream` directly in controller parameters for large uploads.
|
|
246
|
+
|
|
247
|
+
```java
|
|
248
|
+
@PostMapping("/upload")
|
|
249
|
+
public ResponseEntity<Void> upload(InputStream body) throws IOException {
|
|
250
|
+
storageService.store(body);
|
|
251
|
+
return ResponseEntity.accepted().build();
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 10. SpEL — Optional Support
|
|
258
|
+
|
|
259
|
+
Spring Expression Language now understands `Optional` chaining natively.
|
|
260
|
+
|
|
261
|
+
```java
|
|
262
|
+
@Value("#{userService.findUser(#userId)?.orElse('Guest')}")
|
|
263
|
+
private String currentUser;
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 11. Enhanced PathPattern
|
|
269
|
+
|
|
270
|
+
Double-wildcard `**` now works in the middle of patterns (previously end-only).
|
|
271
|
+
|
|
272
|
+
```java
|
|
273
|
+
@GetMapping("/**/docs/{name}")
|
|
274
|
+
public String getDoc(@PathVariable String name) { ... }
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 12. RestTestClient (Testing)
|
|
280
|
+
|
|
281
|
+
`RestTestClient` replaces `TestRestTemplate` and `WebTestClient` for servlet-layer tests.
|
|
282
|
+
|
|
283
|
+
```java
|
|
284
|
+
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
|
285
|
+
class ProductControllerTest {
|
|
286
|
+
|
|
287
|
+
@Autowired
|
|
288
|
+
private RestTestClient client;
|
|
289
|
+
|
|
290
|
+
@Test
|
|
291
|
+
void shouldReturnProducts() {
|
|
292
|
+
client.get().uri("/api/products")
|
|
293
|
+
.exchange()
|
|
294
|
+
.expectStatus().isOk()
|
|
295
|
+
.expectBodyList(Product.class).hasSize(3);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## 13. Removed APIs Migration
|
|
303
|
+
|
|
304
|
+
| Removed | Replacement |
|
|
305
|
+
|---|---|
|
|
306
|
+
| `RestTemplate` | `RestClient` |
|
|
307
|
+
| `JdbcTemplate` (direct) | `JdbcClient` |
|
|
308
|
+
| `javax.*` namespaces | `jakarta.*` |
|
|
309
|
+
| `spring-jcl` | Apache Commons Logging |
|
|
310
|
+
| JUnit 4 | JUnit 5 |
|
|
311
|
+
| Jackson 2.x | Jackson 3.x |
|
|
312
|
+
| XML Spring MVC config | `WebMvcConfigurer` (Java) |
|
|
313
|
+
| `suffixPatternMatch` | Explicit media types |
|
|
314
|
+
| `trailingSlashMatch` | Explicit URI templates |
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Spring Modulith 2.0.x — Reference
|
|
2
|
+
|
|
3
|
+
**Version**: 2.0.3 | **Spring Boot**: 4.0.x | **Purpose**: Domain-driven modular monolith
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
1. [Module Package Structure](#1-module-package-structure)
|
|
9
|
+
2. [Gradle Dependency](#2-gradle-dependency)
|
|
10
|
+
3. [Module Verification Test](#3-module-verification-test)
|
|
11
|
+
4. [@ApplicationModule Annotation](#4-applicationmodule-annotation)
|
|
12
|
+
5. [Event-Driven Inter-Module Communication](#5-event-driven-inter-module-communication)
|
|
13
|
+
6. [Transactional Event Listeners](#6-transactional-event-listeners)
|
|
14
|
+
7. [Module Integration Testing](#7-module-integration-testing)
|
|
15
|
+
8. [Generating Documentation](#8-generating-documentation)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Module Package Structure
|
|
20
|
+
|
|
21
|
+
Each direct sub-package of the application's root package is a **module**.
|
|
22
|
+
Internal types live in sub-packages; only the top-level package is public API.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
com.example.shop/
|
|
26
|
+
├── ShopApplication.java ← root
|
|
27
|
+
├── orders/ ← "orders" module
|
|
28
|
+
│ ├── OrderService.java ← public API
|
|
29
|
+
│ ├── OrderController.java ← public API
|
|
30
|
+
│ └── internal/
|
|
31
|
+
│ ├── OrderRepository.java ← internal
|
|
32
|
+
│ └── OrderMapper.java ← internal
|
|
33
|
+
├── inventory/ ← "inventory" module
|
|
34
|
+
│ ├── InventoryService.java ← public API
|
|
35
|
+
│ └── internal/
|
|
36
|
+
│ └── StockRepository.java ← internal
|
|
37
|
+
└── shared/ ← shared utilities (no module boundary)
|
|
38
|
+
└── Money.java
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Modules **cannot** directly import each other's `internal` sub-packages.
|
|
42
|
+
Cross-module communication happens through **events** or **public service interfaces**.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 2. Gradle Dependency
|
|
47
|
+
|
|
48
|
+
```kotlin
|
|
49
|
+
// build.gradle.kts
|
|
50
|
+
dependencyManagement {
|
|
51
|
+
imports {
|
|
52
|
+
mavenBom("org.springframework.modulith:spring-modulith-bom:2.0.3")
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
dependencies {
|
|
57
|
+
implementation("org.springframework.modulith:spring-modulith-starter-core")
|
|
58
|
+
implementation("org.springframework.modulith:spring-modulith-starter-jpa") // if using JPA
|
|
59
|
+
testImplementation("org.springframework.modulith:spring-modulith-starter-test")
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 3. Module Verification Test
|
|
66
|
+
|
|
67
|
+
Run once per CI pipeline — validates that no module violates boundaries.
|
|
68
|
+
|
|
69
|
+
```java
|
|
70
|
+
@Test
|
|
71
|
+
void applicationModulesShouldBeCompliant() {
|
|
72
|
+
ApplicationModules.of(ShopApplication.class).verify();
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`verify()` throws if:
|
|
77
|
+
- A module imports another module's `internal` package.
|
|
78
|
+
- Circular module dependencies exist.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 4. @ApplicationModule Annotation
|
|
83
|
+
|
|
84
|
+
Fine-tune module visibility and allowed dependencies:
|
|
85
|
+
|
|
86
|
+
```java
|
|
87
|
+
// orders/package-info.java
|
|
88
|
+
@ApplicationModule(
|
|
89
|
+
displayName = "Orders",
|
|
90
|
+
allowedDependencies = {"inventory", "shared"} // only these modules may be imported
|
|
91
|
+
)
|
|
92
|
+
package com.example.shop.orders;
|
|
93
|
+
|
|
94
|
+
import org.springframework.modulith.ApplicationModule;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Named interfaces (explicit public API within a module):
|
|
98
|
+
|
|
99
|
+
```java
|
|
100
|
+
@NamedInterface("api")
|
|
101
|
+
package com.example.shop.orders.api;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 5. Event-Driven Inter-Module Communication
|
|
107
|
+
|
|
108
|
+
Modules communicate via Spring's `ApplicationEventPublisher`. No direct service calls between modules.
|
|
109
|
+
|
|
110
|
+
**Publisher** (orders module):
|
|
111
|
+
|
|
112
|
+
```java
|
|
113
|
+
record OrderPlacedEvent(Long orderId, String productSku, int quantity) {}
|
|
114
|
+
|
|
115
|
+
@Service
|
|
116
|
+
public class OrderService {
|
|
117
|
+
|
|
118
|
+
private final ApplicationEventPublisher events;
|
|
119
|
+
private final JdbcClient jdbc;
|
|
120
|
+
|
|
121
|
+
public Order placeOrder(OrderRequest req) {
|
|
122
|
+
Order order = persistOrder(req);
|
|
123
|
+
events.publishEvent(new OrderPlacedEvent(order.id(), req.sku(), req.quantity()));
|
|
124
|
+
return order;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Listener** (inventory module):
|
|
130
|
+
|
|
131
|
+
```java
|
|
132
|
+
@Service
|
|
133
|
+
public class InventoryService {
|
|
134
|
+
|
|
135
|
+
@ApplicationModuleListener // replaces @EventListener for cross-module events
|
|
136
|
+
public void on(OrderPlacedEvent event) {
|
|
137
|
+
reserveStock(event.productSku(), event.quantity());
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`@ApplicationModuleListener` is async and transactional by default in Modulith.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 6. Transactional Event Listeners
|
|
147
|
+
|
|
148
|
+
For guaranteed "at-least-once" delivery with persistent event logs:
|
|
149
|
+
|
|
150
|
+
```kotlin
|
|
151
|
+
// build.gradle.kts
|
|
152
|
+
implementation("org.springframework.modulith:spring-modulith-events-api")
|
|
153
|
+
implementation("org.springframework.modulith:spring-modulith-events-jpa") // or jdbc, mongodb
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```java
|
|
157
|
+
@Service
|
|
158
|
+
public class OrderService {
|
|
159
|
+
|
|
160
|
+
@Transactional
|
|
161
|
+
public Order placeOrder(OrderRequest req) {
|
|
162
|
+
Order order = persistOrder(req);
|
|
163
|
+
// Event is stored in the event publication log within the same transaction.
|
|
164
|
+
// Published to listeners after commit — even survives a crash.
|
|
165
|
+
events.publishEvent(new OrderPlacedEvent(order.id(), req.sku(), req.quantity()));
|
|
166
|
+
return order;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Incomplete publications are retried automatically on next application start.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 7. Module Integration Testing
|
|
176
|
+
|
|
177
|
+
Test a single module in isolation with only its direct dependencies.
|
|
178
|
+
|
|
179
|
+
```java
|
|
180
|
+
@ApplicationModuleTest // boots only the "orders" module + stubs
|
|
181
|
+
class OrderServiceTest {
|
|
182
|
+
|
|
183
|
+
@Autowired OrderService orderService;
|
|
184
|
+
@MockitoBean InventoryService inventoryService; // stub cross-module dependency
|
|
185
|
+
|
|
186
|
+
@Test
|
|
187
|
+
void shouldPlaceOrderAndPublishEvent() {
|
|
188
|
+
var result = orderService.placeOrder(new OrderRequest("SKU-001", 2));
|
|
189
|
+
assertThat(result.id()).isNotNull();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Bootstrap modes:
|
|
195
|
+
- `STANDALONE` — only the annotated module (default)
|
|
196
|
+
- `DIRECT_DEPENDENCIES` — module + direct dependencies
|
|
197
|
+
- `ALL_DEPENDENCIES` — full transitive graph
|
|
198
|
+
|
|
199
|
+
```java
|
|
200
|
+
@ApplicationModuleTest(mode = BootstrapMode.DIRECT_DEPENDENCIES)
|
|
201
|
+
class OrderServiceTest { ... }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 8. Generating Documentation
|
|
207
|
+
|
|
208
|
+
Produces AsciiDoc / PlantUML diagrams of module dependencies:
|
|
209
|
+
|
|
210
|
+
```java
|
|
211
|
+
@Test
|
|
212
|
+
void generateModuleDocumentation() throws Exception {
|
|
213
|
+
var modules = ApplicationModules.of(ShopApplication.class);
|
|
214
|
+
new Documenter(modules)
|
|
215
|
+
.writeDocumentation()
|
|
216
|
+
.writeIndividualModulesAsPlantUml();
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Output lands in `target/spring-modulith-docs/` (Maven) or `build/spring-modulith-docs/` (Gradle).
|
package/skills-lock.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"skills": {
|
|
4
|
+
"agent-browser": {
|
|
5
|
+
"source": "vercel-labs/agent-browser",
|
|
6
|
+
"sourceType": "github",
|
|
7
|
+
"computedHash": "69e805b9bba58a76e3f9ac835d506a4f06f18a7867d2ba5e23e40638f6f351eb"
|
|
8
|
+
},
|
|
9
|
+
"find-skills": {
|
|
10
|
+
"source": "vercel-labs/skills",
|
|
11
|
+
"sourceType": "github",
|
|
12
|
+
"computedHash": "6412eb4eb3b91595ebab937f0c69501240e7ba761b3d82510b5cf506ec5c7adc"
|
|
13
|
+
},
|
|
14
|
+
"skill-creator": {
|
|
15
|
+
"source": "anthropics/skills",
|
|
16
|
+
"sourceType": "github",
|
|
17
|
+
"computedHash": "a096b9af85c9d954374ebe3f7a23b5b7737ed3f35fe2a8f5893742c4e00dba1c"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import org.springframework.boot.gradle.tasks.bundling.BootJar
|
|
2
|
+
|
|
3
|
+
plugins {
|
|
4
|
+
java
|
|
5
|
+
id("org.springframework.boot") version "{{bootVersion}}"
|
|
6
|
+
id("io.spring.dependency-management") version "1.1.7"
|
|
7
|
+
{{#if hasNative}} id("org.graalvm.buildtools.native") version "0.10.4"
|
|
8
|
+
{{/if}}}
|
|
9
|
+
|
|
10
|
+
group = "{{packageName}}"
|
|
11
|
+
version = "0.0.1-SNAPSHOT"
|
|
12
|
+
|
|
13
|
+
java {
|
|
14
|
+
toolchain {
|
|
15
|
+
languageVersion = JavaLanguageVersion.of({{javaVersion}})
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
{{#if enablePreview}}
|
|
20
|
+
tasks.withType<JavaCompile> {
|
|
21
|
+
options.compilerArgs.addAll(listOf("--enable-preview"))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
tasks.withType<Test> {
|
|
25
|
+
jvmArgs("--enable-preview")
|
|
26
|
+
useJUnitPlatform()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
tasks.named<BootJar>("bootJar") {
|
|
30
|
+
jvmArguments.addAll(listOf("--enable-preview"))
|
|
31
|
+
}
|
|
32
|
+
{{/if}}
|
|
33
|
+
{{#if !enablePreview}}
|
|
34
|
+
tasks.withType<Test> {
|
|
35
|
+
useJUnitPlatform()
|
|
36
|
+
}
|
|
37
|
+
{{/if}}
|
|
38
|
+
|
|
39
|
+
repositories {
|
|
40
|
+
mavenCentral()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
{{#if hasModulith}}
|
|
44
|
+
dependencyManagement {
|
|
45
|
+
imports {
|
|
46
|
+
mavenBom("org.springframework.modulith:spring-modulith-bom:2.0.3")
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
{{/if}}
|
|
50
|
+
|
|
51
|
+
dependencies {
|
|
52
|
+
// --- Web ---
|
|
53
|
+
{{#if hasWebFlux}} implementation("org.springframework.boot:spring-boot-starter-webflux")
|
|
54
|
+
{{/if}}
|
|
55
|
+
{{#if !hasWebFlux}} implementation("org.springframework.boot:spring-boot-starter-web")
|
|
56
|
+
{{/if}}
|
|
57
|
+
|
|
58
|
+
// --- Data ---
|
|
59
|
+
{{#if hasJpa}} implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
|
60
|
+
implementation("org.springframework.boot:spring-boot-starter-jdbc")
|
|
61
|
+
{{/if}}
|
|
62
|
+
{{#if hasMongo}} implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
|
|
63
|
+
{{/if}}
|
|
64
|
+
{{dbDependency}}
|
|
65
|
+
|
|
66
|
+
// --- Validation ---
|
|
67
|
+
{{#if hasValidation}} implementation("org.springframework.boot:spring-boot-starter-validation")
|
|
68
|
+
{{/if}}
|
|
69
|
+
|
|
70
|
+
// --- Security ---
|
|
71
|
+
{{#if hasSecurity}} implementation("org.springframework.boot:spring-boot-starter-security")
|
|
72
|
+
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
|
|
73
|
+
{{/if}}
|
|
74
|
+
|
|
75
|
+
// --- Observability ---
|
|
76
|
+
{{#if hasActuator}} implementation("org.springframework.boot:spring-boot-starter-actuator")
|
|
77
|
+
implementation("io.micrometer:micrometer-tracing-bridge-otel")
|
|
78
|
+
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
|
|
79
|
+
runtimeOnly("io.micrometer:micrometer-registry-prometheus")
|
|
80
|
+
{{/if}}
|
|
81
|
+
|
|
82
|
+
// --- Null Safety (JSpecify) ---
|
|
83
|
+
implementation("org.jspecify:jspecify:1.0.0")
|
|
84
|
+
|
|
85
|
+
{{#if hasModulith}} // --- Spring Modulith ---
|
|
86
|
+
implementation("org.springframework.modulith:spring-modulith-starter-core")
|
|
87
|
+
{{modulithDataDep}}
|
|
88
|
+
{{/if}}
|
|
89
|
+
|
|
90
|
+
// --- Dev Tools ---
|
|
91
|
+
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
|
92
|
+
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
|
93
|
+
|
|
94
|
+
// --- Testing ---
|
|
95
|
+
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
|
96
|
+
{{#if hasSecurity}} testImplementation("org.springframework.security:spring-security-test")
|
|
97
|
+
{{/if}}
|
|
98
|
+
{{#if hasModulith}} testImplementation("org.springframework.modulith:spring-modulith-starter-test")
|
|
99
|
+
{{/if}}
|
|
100
|
+
{{#if hasJpa}} testImplementation("org.testcontainers:postgresql")
|
|
101
|
+
testImplementation("org.testcontainers:junit-jupiter")
|
|
102
|
+
{{/if}}
|
|
103
|
+
}
|
|
104
|
+
{{#if hasNative}}
|
|
105
|
+
graalvmNative {
|
|
106
|
+
binaries {
|
|
107
|
+
named("main") {
|
|
108
|
+
imageName.set("{{projectName}}")
|
|
109
|
+
buildArgs.add("--enable-preview")
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
{{/if}}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
services:
|
|
2
|
+
{{#if hasJpa}} postgres:
|
|
3
|
+
image: postgres:17
|
|
4
|
+
environment:
|
|
5
|
+
POSTGRES_DB: {{projectName}}
|
|
6
|
+
POSTGRES_USER: app
|
|
7
|
+
POSTGRES_PASSWORD: secret
|
|
8
|
+
ports:
|
|
9
|
+
- "5432:5432"
|
|
10
|
+
volumes:
|
|
11
|
+
- pgdata:/var/lib/postgresql/data
|
|
12
|
+
healthcheck:
|
|
13
|
+
test: ["CMD-SHELL", "pg_isready -U app"]
|
|
14
|
+
interval: 10s
|
|
15
|
+
timeout: 5s
|
|
16
|
+
retries: 5
|
|
17
|
+
{{/if}}
|
|
18
|
+
|
|
19
|
+
{{#if hasMongo}} mongodb:
|
|
20
|
+
image: mongo:7.0
|
|
21
|
+
ports:
|
|
22
|
+
- "27017:27017"
|
|
23
|
+
environment:
|
|
24
|
+
MONGO_INITDB_DATABASE: {{projectName}}
|
|
25
|
+
volumes:
|
|
26
|
+
- mongodata:/data/db
|
|
27
|
+
{{/if}}
|
|
28
|
+
|
|
29
|
+
{{#if hasActuator}} otel-collector:
|
|
30
|
+
image: otel/opentelemetry-collector-contrib:latest
|
|
31
|
+
ports:
|
|
32
|
+
- "4317:4317" # gRPC
|
|
33
|
+
- "4318:4318" # HTTP
|
|
34
|
+
{{/if}}
|
|
35
|
+
|
|
36
|
+
volumes:
|
|
37
|
+
{{#if hasJpa}} pgdata:
|
|
38
|
+
{{/if}}
|
|
39
|
+
{{#if hasMongo}} mongodata:
|
|
40
|
+
{{/if}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rootProject.name = "{{projectName}}"
|