@relipa/ai-flow-kit 0.0.5 → 0.0.6-beta.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 +81 -69
- package/bin/aiflow.js +46 -2
- package/custom/rules/java/spring-boot-examples.md +329 -0
- package/custom/skills/read-study-requirement/SKILL.md +54 -0
- package/custom/skills/review-plan/SKILL.md +21 -0
- package/custom/templates/shared/gate-workflow.md +88 -88
- package/custom/templates/spring-boot.md +224 -523
- package/docs/common/AIFLOW.md +501 -462
- package/docs/common/CHANGELOG.md +162 -132
- package/docs/common/QUICK_START.md +44 -12
- package/docs/common/cli-reference.md +115 -6
- package/docs/common/getting-started.md +3 -0
- package/docs/project/ARCHITECTURE.md +28 -28
- package/package.json +6 -1
- package/scripts/doctor.js +89 -48
- package/scripts/guide.js +28 -5
- package/scripts/hooks/session-start.js +144 -45
- package/scripts/init.js +109 -50
- package/scripts/prompt.js +431 -431
- package/scripts/task.js +384 -0
- package/scripts/telemetry/cli.js +243 -243
- package/scripts/telemetry/config.js +91 -91
- package/scripts/telemetry/crypto.js +20 -20
- package/scripts/telemetry/flush.js +162 -162
- package/scripts/telemetry/record.js +138 -138
- package/scripts/use.js +94 -2
|
@@ -1,523 +1,224 @@
|
|
|
1
|
-
# Spring Boot AI System Prompt
|
|
2
|
-
|
|
3
|
-
You are an expert Java Spring Boot developer. Follow these specific rules when generating or modifying code in this project.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
↓
|
|
29
|
-
|
|
30
|
-
↓
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
├──
|
|
43
|
-
|
|
44
|
-
├──
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
├──
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
├──
|
|
52
|
-
└──
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
- Use
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
public class UserResponse {
|
|
226
|
-
private Long id;
|
|
227
|
-
private String email;
|
|
228
|
-
private String fullName;
|
|
229
|
-
private LocalDateTime createdAt;
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
## MapStruct Mapper Rules
|
|
236
|
-
|
|
237
|
-
- Use `\@Mapper(componentModel = "spring")` — inject as a Spring bean
|
|
238
|
-
- Define explicit mappings with `\@Mapping` when field names differ
|
|
239
|
-
- Never do manual mapping (`new DTO(); dto.setField(entity.getField())`)
|
|
240
|
-
|
|
241
|
-
```java
|
|
242
|
-
\@Mapper(componentModel = "spring")
|
|
243
|
-
public interface UserMapper {
|
|
244
|
-
|
|
245
|
-
\@Mapping(target = "createdAt", source = "createdAt")
|
|
246
|
-
UserResponse toResponse(User user);
|
|
247
|
-
|
|
248
|
-
\@Mapping(target = "id", ignore = true)
|
|
249
|
-
\@Mapping(target = "deleted", ignore = true)
|
|
250
|
-
\@Mapping(target = "createdAt", ignore = true)
|
|
251
|
-
\@Mapping(target = "updatedAt", ignore = true)
|
|
252
|
-
User toEntity(CreateUserRequest request);
|
|
253
|
-
|
|
254
|
-
List<UserResponse> toResponseList(List<User> users);
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
---
|
|
259
|
-
|
|
260
|
-
## Exception Handling
|
|
261
|
-
|
|
262
|
-
- Create custom exceptions extending `RuntimeException`
|
|
263
|
-
- Handle all exceptions in one `\@RestControllerAdvice` class
|
|
264
|
-
- Return a consistent error response format
|
|
265
|
-
- Never expose stack traces to the client
|
|
266
|
-
|
|
267
|
-
```java
|
|
268
|
-
// Custom exception
|
|
269
|
-
public class ResourceNotFoundException extends RuntimeException {
|
|
270
|
-
public ResourceNotFoundException(String resource, Long id) {
|
|
271
|
-
super(String.format("%s with id %d not found", resource, id));
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Global handler
|
|
276
|
-
\@RestControllerAdvice
|
|
277
|
-
\@Slf4j
|
|
278
|
-
public class GlobalExceptionHandler {
|
|
279
|
-
|
|
280
|
-
\@ExceptionHandler(ResourceNotFoundException.class)
|
|
281
|
-
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
|
|
282
|
-
log.warn("Resource not found: {}", ex.getMessage());
|
|
283
|
-
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
|
284
|
-
.body(ErrorResponse.of("NOT_FOUND", ex.getMessage()));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
\@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
288
|
-
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
|
|
289
|
-
List<String> errors = ex.getBindingResult()
|
|
290
|
-
.getFieldErrors()
|
|
291
|
-
.stream()
|
|
292
|
-
.map(e -> e.getField() + ": " + e.getDefaultMessage())
|
|
293
|
-
.toList();
|
|
294
|
-
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
|
295
|
-
.body(ErrorResponse.of("VALIDATION_FAILED", errors.toString()));
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
\@ExceptionHandler(Exception.class)
|
|
299
|
-
public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
|
|
300
|
-
log.error("Unexpected error", ex);
|
|
301
|
-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
302
|
-
.body(ErrorResponse.of("INTERNAL_ERROR", "An unexpected error occurred"));
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
---
|
|
308
|
-
|
|
309
|
-
## Testing Rules
|
|
310
|
-
|
|
311
|
-
### Unit Tests (Service layer)
|
|
312
|
-
- Test class: `[ServiceImpl]Test.java`
|
|
313
|
-
- Mock all dependencies with `\@ExtendWith(MockitoExtension.class)` + `\@Mock`
|
|
314
|
-
- Test happy path + edge cases + exception scenarios
|
|
315
|
-
- Use AssertJ: `assertThat(result).isEqualTo(expected)`
|
|
316
|
-
|
|
317
|
-
```java
|
|
318
|
-
\@ExtendWith(MockitoExtension.class)
|
|
319
|
-
class UserServiceImplTest {
|
|
320
|
-
|
|
321
|
-
\@Mock
|
|
322
|
-
private UserRepository userRepository;
|
|
323
|
-
|
|
324
|
-
\@Mock
|
|
325
|
-
private UserMapper userMapper;
|
|
326
|
-
|
|
327
|
-
\@InjectMocks
|
|
328
|
-
private UserServiceImpl userService;
|
|
329
|
-
|
|
330
|
-
\@Test
|
|
331
|
-
void create_shouldReturnUserResponse_whenEmailIsUnique() {
|
|
332
|
-
// Arrange
|
|
333
|
-
CreateUserRequest request = CreateUserRequest.builder()
|
|
334
|
-
.email("test\@example.com")
|
|
335
|
-
.fullName("Test User")
|
|
336
|
-
.build();
|
|
337
|
-
User user = User.builder().id(1L).email("test\@example.com").build();
|
|
338
|
-
UserResponse expected = UserResponse.builder().id(1L).build();
|
|
339
|
-
|
|
340
|
-
given(userRepository.existsByEmail(request.getEmail())).willReturn(false);
|
|
341
|
-
given(userMapper.toEntity(request)).willReturn(user);
|
|
342
|
-
given(userRepository.save(user)).willReturn(user);
|
|
343
|
-
given(userMapper.toResponse(user)).willReturn(expected);
|
|
344
|
-
|
|
345
|
-
// Act
|
|
346
|
-
UserResponse result = userService.create(request);
|
|
347
|
-
|
|
348
|
-
// Assert
|
|
349
|
-
assertThat(result).isEqualTo(expected);
|
|
350
|
-
verify(userRepository).save(user);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
\@Test
|
|
354
|
-
void create_shouldThrowDuplicateEmailException_whenEmailAlreadyExists() {
|
|
355
|
-
CreateUserRequest request = CreateUserRequest.builder()
|
|
356
|
-
.email("existing\@example.com").build();
|
|
357
|
-
given(userRepository.existsByEmail(request.getEmail())).willReturn(true);
|
|
358
|
-
|
|
359
|
-
assertThatThrownBy(() -> userService.create(request))
|
|
360
|
-
.isInstanceOf(DuplicateEmailException.class);
|
|
361
|
-
verify(userRepository, never()).save(any());
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### Integration Tests (Controller layer)
|
|
367
|
-
- Use `\@SpringBootTest` + `\@AutoConfigureMockMvc`
|
|
368
|
-
- Test full HTTP flow with `MockMvc`
|
|
369
|
-
- Use `\@Sql` or Testcontainers for database state
|
|
370
|
-
|
|
371
|
-
```java
|
|
372
|
-
\@SpringBootTest
|
|
373
|
-
\@AutoConfigureMockMvc
|
|
374
|
-
class UserControllerIntegrationTest {
|
|
375
|
-
|
|
376
|
-
\@Autowired
|
|
377
|
-
private MockMvc mockMvc;
|
|
378
|
-
|
|
379
|
-
\@Autowired
|
|
380
|
-
private ObjectMapper objectMapper;
|
|
381
|
-
|
|
382
|
-
\@Test
|
|
383
|
-
void createUser_shouldReturn201_whenRequestIsValid() throws Exception {
|
|
384
|
-
CreateUserRequest request = CreateUserRequest.builder()
|
|
385
|
-
.email("test\@example.com")
|
|
386
|
-
.fullName("Test User")
|
|
387
|
-
.password("password123")
|
|
388
|
-
.build();
|
|
389
|
-
|
|
390
|
-
mockMvc.perform(post("/api/v1/users")
|
|
391
|
-
.contentType(MediaType.APPLICATION_JSON)
|
|
392
|
-
.content(objectMapper.writeValueAsString(request)))
|
|
393
|
-
.andExpect(status().isCreated())
|
|
394
|
-
.andExpect(jsonPath("$.email").value("test\@example.com"))
|
|
395
|
-
.andExpect(jsonPath("$.id").isNumber());
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
\@Test
|
|
399
|
-
void createUser_shouldReturn400_whenEmailIsInvalid() throws Exception {
|
|
400
|
-
CreateUserRequest request = CreateUserRequest.builder()
|
|
401
|
-
.email("not-an-email")
|
|
402
|
-
.fullName("Test")
|
|
403
|
-
.password("password123")
|
|
404
|
-
.build();
|
|
405
|
-
|
|
406
|
-
mockMvc.perform(post("/api/v1/users")
|
|
407
|
-
.contentType(MediaType.APPLICATION_JSON)
|
|
408
|
-
.content(objectMapper.writeValueAsString(request)))
|
|
409
|
-
.andExpect(status().isBadRequest())
|
|
410
|
-
.andExpect(jsonPath("$.code").value("VALIDATION_FAILED"));
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
---
|
|
416
|
-
|
|
417
|
-
## Naming Conventions
|
|
418
|
-
|
|
419
|
-
| Element | Convention | Example |
|
|
420
|
-
|---------|-----------|---------|
|
|
421
|
-
| Class | PascalCase | `UserService`, `OrderController` |
|
|
422
|
-
| Method | camelCase | `findById`, `createOrder` |
|
|
423
|
-
| Variable | camelCase | `userResponse`, `orderId` |
|
|
424
|
-
| Constant | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
|
425
|
-
| Package | lowercase | `com.company.project.service` |
|
|
426
|
-
| DB Table | snake_case | `user_orders`, `product_items` |
|
|
427
|
-
| DB Column | snake_case | `created_at`, `user_id` |
|
|
428
|
-
| REST endpoint | kebab-case | `/api/v1/user-profiles` |
|
|
429
|
-
| Request DTO | `[Action][Resource]Request` | `CreateOrderRequest` |
|
|
430
|
-
| Response DTO | `[Resource]Response` | `OrderResponse` |
|
|
431
|
-
|
|
432
|
-
---
|
|
433
|
-
|
|
434
|
-
## API Design
|
|
435
|
-
|
|
436
|
-
- Version all APIs: `/api/v1/...`
|
|
437
|
-
- Use plural nouns for resources: `/users`, `/orders`
|
|
438
|
-
- HTTP methods: `GET` (read), `POST` (create), `PUT` (full update), `PATCH` (partial), `DELETE`
|
|
439
|
-
- Return `201 Created` for POST, `200 OK` for GET/PUT/PATCH, `204 No Content` for DELETE
|
|
440
|
-
- Use consistent pagination: `?page=0&size=20&sort=createdAt,desc`
|
|
441
|
-
|
|
442
|
-
```
|
|
443
|
-
GET /api/v1/users → 200 list
|
|
444
|
-
POST /api/v1/users → 201 created
|
|
445
|
-
GET /api/v1/users/{id} → 200 or 404
|
|
446
|
-
PUT /api/v1/users/{id} → 200 or 404
|
|
447
|
-
DELETE /api/v1/users/{id} → 204 or 404
|
|
448
|
-
GET /api/v1/users/{id}/orders → 200 nested resource
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
---
|
|
452
|
-
|
|
453
|
-
## Performance Rules
|
|
454
|
-
|
|
455
|
-
- Always use pagination — never return unbounded lists
|
|
456
|
-
- Avoid N+1: use `\@EntityGraph` or `JOIN FETCH` in JPQL
|
|
457
|
-
- Add database indexes on frequently queried columns
|
|
458
|
-
- Use `\@Transactional(readOnly = true)` on read-only service methods
|
|
459
|
-
- For heavy read operations, consider projection interfaces
|
|
460
|
-
|
|
461
|
-
```java
|
|
462
|
-
// ✅ Read-only transaction
|
|
463
|
-
\@Transactional(readOnly = true)
|
|
464
|
-
public Page<UserResponse> findAll(Pageable pageable) {
|
|
465
|
-
return userRepository.findAll(pageable).map(userMapper::toResponse);
|
|
466
|
-
}
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
---
|
|
470
|
-
|
|
471
|
-
## Security Rules
|
|
472
|
-
|
|
473
|
-
- Never log passwords, tokens, or sensitive PII
|
|
474
|
-
- Hash passwords with BCrypt: `passwordEncoder.encode(rawPassword)`
|
|
475
|
-
- Validate and sanitize all user inputs via Bean Validation
|
|
476
|
-
- Use `\@PreAuthorize` for method-level security
|
|
477
|
-
- Never return stack traces to API consumers
|
|
478
|
-
- Store secrets in environment variables / Vault — never in code
|
|
479
|
-
|
|
480
|
-
---
|
|
481
|
-
|
|
482
|
-
## Logging Rules
|
|
483
|
-
|
|
484
|
-
- Use SLF4J with Lombok `\@Slf4j`
|
|
485
|
-
- `log.info` — normal business events
|
|
486
|
-
- `log.warn` — recoverable issues (not found, validation fail)
|
|
487
|
-
- `log.error` — unexpected exceptions (always include `ex` as the second argument)
|
|
488
|
-
- Never log sensitive data (password, credit card, token)
|
|
489
|
-
|
|
490
|
-
```java
|
|
491
|
-
\@Slf4j
|
|
492
|
-
\@Service
|
|
493
|
-
public class UserServiceImpl implements UserService {
|
|
494
|
-
|
|
495
|
-
public UserResponse findById(Long id) {
|
|
496
|
-
log.info("Finding user by id: {}", id);
|
|
497
|
-
return userRepository.findById(id)
|
|
498
|
-
.map(userMapper::toResponse)
|
|
499
|
-
.orElseThrow(() -> {
|
|
500
|
-
log.warn("User not found: id={}", id);
|
|
501
|
-
return new ResourceNotFoundException("User", id);
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
---
|
|
508
|
-
|
|
509
|
-
## Common Anti-Patterns to Avoid
|
|
510
|
-
|
|
511
|
-
- ❌ `\@Autowired` field injection → use constructor injection (Lombok `\@RequiredArgsConstructor`)
|
|
512
|
-
- ❌ `\@Data` on JPA entities → use `\@Getter \@Setter` separately
|
|
513
|
-
- ❌ Returning `Entity` directly from Controller → always use DTO
|
|
514
|
-
- ❌ `SELECT *` or unbounded `findAll()` → always paginate
|
|
515
|
-
- ❌ Business logic in Controller → move to Service
|
|
516
|
-
- ❌ Catching and swallowing exceptions → handle properly or rethrow
|
|
517
|
-
- ❌ `new RuntimeException("something")` → create a specific custom exception
|
|
518
|
-
- ❌ Hardcoding config values → use `\@Value` or `\@ConfigurationProperties`
|
|
519
|
-
- ❌ `\@Transactional` on Controller → only on Service methods
|
|
520
|
-
|
|
521
|
-
---
|
|
522
|
-
|
|
523
|
-
When explaining changes, refer to the [Spring Boot Official Documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/) and [Spring Data JPA](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/) conventions.
|
|
1
|
+
# Spring Boot AI System Prompt
|
|
2
|
+
|
|
3
|
+
You are an expert Java Spring Boot developer. Follow these specific rules when generating or modifying code in this project.
|
|
4
|
+
|
|
5
|
+
> **Code examples:** See `.rules/java/spring-boot-examples.md` — read it when generating code for a specific layer.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Project Stack
|
|
10
|
+
|
|
11
|
+
- **Java:** 17+
|
|
12
|
+
- **Spring Boot:** 3.x
|
|
13
|
+
- **Build Tool:** Maven (prefer) or Gradle
|
|
14
|
+
- **ORM:** Spring Data JPA + Hibernate
|
|
15
|
+
- **Database:** MySQL / PostgreSQL
|
|
16
|
+
- **Testing:** JUnit 5 + Mockito + AssertJ
|
|
17
|
+
- **Utilities:** Lombok, MapStruct
|
|
18
|
+
- **API Style:** RESTful JSON
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
Follow strict **Layered Architecture**:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Controller (HTTP layer)
|
|
28
|
+
↓
|
|
29
|
+
Service (Business logic)
|
|
30
|
+
↓
|
|
31
|
+
Repository (Data access - Spring Data JPA)
|
|
32
|
+
↓
|
|
33
|
+
Entity / Domain Model
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Package Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
com.company.project/
|
|
40
|
+
├── controller/ # \@RestController — HTTP endpoints only
|
|
41
|
+
├── service/
|
|
42
|
+
│ ├── impl/ # \@Service — business logic implementation
|
|
43
|
+
│ └── [Interface].java
|
|
44
|
+
├── repository/ # \@Repository — extends JpaRepository
|
|
45
|
+
├── entity/ # \@Entity — JPA entities
|
|
46
|
+
├── dto/
|
|
47
|
+
│ ├── request/ # Input DTOs (e.g. CreateUserRequest)
|
|
48
|
+
│ └── response/ # Output DTOs (e.g. UserResponse)
|
|
49
|
+
├── mapper/ # MapStruct mappers (Entity ↔ DTO)
|
|
50
|
+
├── exception/
|
|
51
|
+
│ ├── GlobalExceptionHandler.java # \@RestControllerAdvice
|
|
52
|
+
│ └── [CustomException].java
|
|
53
|
+
├── config/ # \@Configuration classes
|
|
54
|
+
└── util/ # Pure utility helpers (stateless)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Controller Rules
|
|
60
|
+
|
|
61
|
+
- Annotate with `\@RestController` + `\@RequestMapping`
|
|
62
|
+
- **Only** handle HTTP concerns: parse request, call service, return response
|
|
63
|
+
- Never put business logic in Controller
|
|
64
|
+
- Always use DTOs — never expose Entity directly
|
|
65
|
+
- Use `ResponseEntity<T>` for explicit HTTP status control
|
|
66
|
+
- Validate input with `\@Valid` + Bean Validation annotations
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Service Rules
|
|
71
|
+
|
|
72
|
+
- Always define an **interface**, implement in the `impl/` package
|
|
73
|
+
- Annotate implementation with `\@Service`
|
|
74
|
+
- All business logic lives here
|
|
75
|
+
- Use `\@Transactional` at the method level (not class level)
|
|
76
|
+
- Throw specific custom exceptions, not generic `RuntimeException`
|
|
77
|
+
- Never return an Entity — always convert to DTO via Mapper
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Repository Rules
|
|
82
|
+
|
|
83
|
+
- Extend `JpaRepository<Entity, ID>`
|
|
84
|
+
- Use **method name queries** for simple queries
|
|
85
|
+
- Use `\@Query` (JPQL) for complex queries — avoid native SQL unless necessary
|
|
86
|
+
- Never add business logic here
|
|
87
|
+
- Use `\@EntityGraph` to solve N+1 problems
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Entity Rules
|
|
92
|
+
|
|
93
|
+
- Use Lombok: `\@Getter`, `\@Setter`, `\@NoArgsConstructor`, `\@AllArgsConstructor`, `\@Builder`
|
|
94
|
+
- Avoid `\@Data` on entities (causes issues with `equals/hashCode` + lazy loading)
|
|
95
|
+
- Always use `\@Table(name = "snake_case_table_name")`
|
|
96
|
+
- Use `\@Column(name = "snake_case_column_name")` explicitly
|
|
97
|
+
- For soft delete: add `deleted` boolean + `deletedAt` timestamp
|
|
98
|
+
- Extend `BaseEntity` for audit fields (`createdAt`, `updatedAt`)
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## DTO Rules
|
|
103
|
+
|
|
104
|
+
- Use separate DTOs for **Request** and **Response** — never share
|
|
105
|
+
- Use Lombok: `\@Getter`, `\@Builder`, `\@AllArgsConstructor`, `\@NoArgsConstructor`
|
|
106
|
+
- Validate in the Request DTO with Bean Validation (`\@NotBlank`, `\@Email`, `\@NotNull`, `\@Size`)
|
|
107
|
+
- Never expose internal fields (password hash, audit timestamps) in the Response DTO
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## MapStruct Mapper Rules
|
|
112
|
+
|
|
113
|
+
- Use `\@Mapper(componentModel = "spring")` — inject as a Spring bean
|
|
114
|
+
- Define explicit mappings with `\@Mapping` when field names differ
|
|
115
|
+
- Never do manual mapping (`new DTO(); dto.setField(entity.getField())`)
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Exception Handling
|
|
120
|
+
|
|
121
|
+
- Create custom exceptions extending `RuntimeException`
|
|
122
|
+
- Handle all exceptions in one `\@RestControllerAdvice` class
|
|
123
|
+
- Return a consistent error response format
|
|
124
|
+
- Never expose stack traces to the client
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Testing Rules
|
|
129
|
+
|
|
130
|
+
### Unit Tests (Service layer)
|
|
131
|
+
- Test class: `[ServiceImpl]Test.java`
|
|
132
|
+
- Mock all dependencies with `\@ExtendWith(MockitoExtension.class)` + `\@Mock`
|
|
133
|
+
- Test happy path + edge cases + exception scenarios
|
|
134
|
+
- Use AssertJ: `assertThat(result).isEqualTo(expected)`
|
|
135
|
+
|
|
136
|
+
### Integration Tests (Controller layer)
|
|
137
|
+
- Use `\@SpringBootTest` + `\@AutoConfigureMockMvc`
|
|
138
|
+
- Test full HTTP flow with `MockMvc`
|
|
139
|
+
- Use `\@Sql` or Testcontainers for database state
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Naming Conventions
|
|
144
|
+
|
|
145
|
+
| Element | Convention | Example |
|
|
146
|
+
|---------|-----------|---------|
|
|
147
|
+
| Class | PascalCase | `UserService`, `OrderController` |
|
|
148
|
+
| Method | camelCase | `findById`, `createOrder` |
|
|
149
|
+
| Variable | camelCase | `userResponse`, `orderId` |
|
|
150
|
+
| Constant | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
|
151
|
+
| Package | lowercase | `com.company.project.service` |
|
|
152
|
+
| DB Table | snake_case | `user_orders`, `product_items` |
|
|
153
|
+
| DB Column | snake_case | `created_at`, `user_id` |
|
|
154
|
+
| REST endpoint | kebab-case | `/api/v1/user-profiles` |
|
|
155
|
+
| Request DTO | `[Action][Resource]Request` | `CreateOrderRequest` |
|
|
156
|
+
| Response DTO | `[Resource]Response` | `OrderResponse` |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## API Design
|
|
161
|
+
|
|
162
|
+
- Version all APIs: `/api/v1/...`
|
|
163
|
+
- Use plural nouns for resources: `/users`, `/orders`
|
|
164
|
+
- HTTP methods: `GET` (read), `POST` (create), `PUT` (full update), `PATCH` (partial), `DELETE`
|
|
165
|
+
- Return `201 Created` for POST, `200 OK` for GET/PUT/PATCH, `204 No Content` for DELETE
|
|
166
|
+
- Use consistent pagination: `?page=0&size=20&sort=createdAt,desc`
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
GET /api/v1/users → 200 list
|
|
170
|
+
POST /api/v1/users → 201 created
|
|
171
|
+
GET /api/v1/users/{id} → 200 or 404
|
|
172
|
+
PUT /api/v1/users/{id} → 200 or 404
|
|
173
|
+
DELETE /api/v1/users/{id} → 204 or 404
|
|
174
|
+
GET /api/v1/users/{id}/orders → 200 nested resource
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Performance Rules
|
|
180
|
+
|
|
181
|
+
- Always use pagination — never return unbounded lists
|
|
182
|
+
- Avoid N+1: use `\@EntityGraph` or `JOIN FETCH` in JPQL
|
|
183
|
+
- Add database indexes on frequently queried columns
|
|
184
|
+
- Use `\@Transactional(readOnly = true)` on read-only service methods
|
|
185
|
+
- For heavy read operations, consider projection interfaces
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Security Rules
|
|
190
|
+
|
|
191
|
+
- Never log passwords, tokens, or sensitive PII
|
|
192
|
+
- Hash passwords with BCrypt: `passwordEncoder.encode(rawPassword)`
|
|
193
|
+
- Validate and sanitize all user inputs via Bean Validation
|
|
194
|
+
- Use `\@PreAuthorize` for method-level security
|
|
195
|
+
- Never return stack traces to API consumers
|
|
196
|
+
- Store secrets in environment variables / Vault — never in code
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Logging Rules
|
|
201
|
+
|
|
202
|
+
- Use SLF4J with Lombok `\@Slf4j`
|
|
203
|
+
- `log.info` — normal business events
|
|
204
|
+
- `log.warn` — recoverable issues (not found, validation fail)
|
|
205
|
+
- `log.error` — unexpected exceptions (always include `ex` as the second argument)
|
|
206
|
+
- Never log sensitive data (password, credit card, token)
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Common Anti-Patterns to Avoid
|
|
211
|
+
|
|
212
|
+
- ❌ `\@Autowired` field injection → use constructor injection (Lombok `\@RequiredArgsConstructor`)
|
|
213
|
+
- ❌ `\@Data` on JPA entities → use `\@Getter \@Setter` separately
|
|
214
|
+
- ❌ Returning `Entity` directly from Controller → always use DTO
|
|
215
|
+
- ❌ `SELECT *` or unbounded `findAll()` → always paginate
|
|
216
|
+
- ❌ Business logic in Controller → move to Service
|
|
217
|
+
- ❌ Catching and swallowing exceptions → handle properly or rethrow
|
|
218
|
+
- ❌ `new RuntimeException("something")` → create a specific custom exception
|
|
219
|
+
- ❌ Hardcoding config values → use `\@Value` or `\@ConfigurationProperties`
|
|
220
|
+
- ❌ `\@Transactional` on Controller → only on Service methods
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
When explaining changes, refer to the [Spring Boot Official Documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/) and [Spring Data JPA](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/) conventions.
|