guardian-framework 0.1.10 → 0.1.12

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.
@@ -0,0 +1,498 @@
1
+ # Java/Spring Boot Enterprise Code Generation — DDD + Clean Architecture
2
+
3
+ > Canonical skill for generating production-grade Java code with Spring Boot following Clean Architecture with DDD.
4
+ > All code MUST follow these patterns. Validators enforce compliance.
5
+ >
6
+ > Source: Clean Architecture principles + Spring Boot best practices + DDD patterns from context7 (/jkazama/ddd-java, /ardalis/cleanarchitecture, /websites/spring_io_spring-boot).
7
+
8
+ ---
9
+
10
+ ## 1. Project Structure — Clean Architecture with DDD
11
+
12
+ Every bounded context follows the same 4-layer structure:
13
+
14
+ ```
15
+ module/
16
+ ├── domain/ # Pure domain entities, value objects, events
17
+ │ ├── model/ # Aggregate roots and entities
18
+ │ │ ├── Entity.java
19
+ │ │ └── Status.java
20
+ │ ├── vo/ # Value objects
21
+ │ │ └── Money.java
22
+ │ ├── event/ # Domain event payloads
23
+ │ │ └── EntityEvent.java
24
+ │ ├── repository/ # Repository interfaces
25
+ │ │ └── EntityRepository.java
26
+ │ └── error/ # Typed exception classes
27
+ │ └── DomainError.java
28
+ ├── application/ # Service interfaces and DTOs
29
+ │ ├── service/ # Service interfaces
30
+ │ │ └── EntityService.java
31
+ │ ├── dto/ # Input/Output DTOs
32
+ │ │ ├── Command.java
33
+ │ │ └── EntityResponse.java
34
+ │ └── impl/ # Service implementations
35
+ │ └── EntityServiceImpl.java
36
+ ├── infrastructure/ # Repository implementations, external adapters
37
+ │ ├── repository/ # JPA repository implementations
38
+ │ │ └── JpaEntityRepository.java
39
+ │ └── persistence/ # JPA entities, migrations
40
+ └── interfaces/ # API contracts (HTTP)
41
+ ├── rest/ # REST controllers
42
+ │ └── EntityController.java
43
+ ├── dto/ # Request/Response DTOs
44
+ └── advice/ # Exception handlers
45
+ └── GlobalExceptionHandler.java
46
+ ```
47
+
48
+ ### Dependency Direction Rule
49
+
50
+ ```
51
+ domain → application → infrastructure → interfaces
52
+ ```
53
+
54
+ - **domain/** — pure Java, no Spring annotations
55
+ - **application/** — depends on domain, uses Spring `@Service`
56
+ - **infrastructure/** — depends on domain + application, uses Spring Data JPA
57
+ - **interfaces/** — depends on application, uses Spring `@RestController`
58
+
59
+ ---
60
+
61
+ ## 2. Domain Layer
62
+
63
+ ### Entity
64
+
65
+ ```java
66
+ // domain/model/Entity.java
67
+
68
+ package com.project.module.domain.model;
69
+
70
+ import com.project.module.domain.vo.Money;
71
+ import com.project.module.domain.error.DomainError;
72
+ import java.time.Instant;
73
+ import java.util.UUID;
74
+
75
+ public class Entity {
76
+ private final UUID id;
77
+ private Status status;
78
+ private final Instant createdAt;
79
+ private Instant updatedAt;
80
+
81
+ public Entity() {
82
+ this.id = UUID.randomUUID();
83
+ this.status = Status.PENDING;
84
+ this.createdAt = Instant.now();
85
+ this.updatedAt = Instant.now();
86
+ }
87
+
88
+ public UUID getId() { return id; }
89
+ public Status getStatus() { return status; }
90
+ public Instant getCreatedAt() { return createdAt; }
91
+
92
+ public void transition(Status newStatus) {
93
+ if (!status.canTransitionTo(newStatus)) {
94
+ throw DomainError.invalidTransition(status, newStatus);
95
+ }
96
+ this.status = newStatus;
97
+ this.updatedAt = Instant.now();
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### Value Object
103
+
104
+ ```java
105
+ // domain/vo/Money.java
106
+
107
+ package com.project.module.domain.vo;
108
+
109
+ import com.project.module.domain.error.DomainError;
110
+
111
+ public record Money(long amount, String currency) {
112
+
113
+ public Money {
114
+ if (amount < 0) throw new DomainError("Amount must be non-negative");
115
+ if (currency == null || currency.length() != 3)
116
+ throw new DomainError("Currency must be ISO 4217");
117
+ }
118
+
119
+ public Money add(Money other) {
120
+ if (!this.currency.equals(other.currency)) {
121
+ throw new DomainError("Currency mismatch");
122
+ }
123
+ return new Money(this.amount + other.amount, this.currency);
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### Repository Interface
129
+
130
+ ```java
131
+ // domain/repository/EntityRepository.java
132
+
133
+ package com.project.module.domain.repository;
134
+
135
+ import com.project.module.domain.model.Entity;
136
+ import com.project.module.domain.model.Status;
137
+ import java.util.List;
138
+ import java.util.Optional;
139
+ import java.util.UUID;
140
+
141
+ public interface EntityRepository {
142
+ Optional<Entity> findById(UUID id);
143
+ void save(Entity entity);
144
+ void delete(UUID id);
145
+ List<Entity> findByStatus(Status status);
146
+ }
147
+ ```
148
+
149
+ ### Status Enum with Transitions
150
+
151
+ ```java
152
+ // domain/model/Status.java
153
+
154
+ public enum Status {
155
+ PENDING,
156
+ ACTIVE,
157
+ COMPLETED,
158
+ FAILED;
159
+
160
+ public boolean canTransitionTo(Status target) {
161
+ return switch (this) {
162
+ case PENDING -> target == ACTIVE;
163
+ case ACTIVE -> target == COMPLETED || target == FAILED;
164
+ case COMPLETED, FAILED -> false;
165
+ };
166
+ }
167
+ }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## 3. Domain Error Handling
173
+
174
+ ```java
175
+ // domain/error/DomainError.java
176
+
177
+ package com.project.module.domain.error;
178
+
179
+ public class DomainError extends RuntimeException {
180
+
181
+ private final ErrorCode code;
182
+
183
+ public DomainError(String message) {
184
+ super(message);
185
+ this.code = ErrorCode.VALIDATION;
186
+ }
187
+
188
+ public DomainError(ErrorCode code, String message) {
189
+ super(message);
190
+ this.code = code;
191
+ }
192
+
193
+ public ErrorCode getCode() { return code; }
194
+
195
+ public static DomainError notFound(String id) {
196
+ return new DomainError(ErrorCode.NOT_FOUND, "Resource " + id + " not found");
197
+ }
198
+
199
+ public static DomainError invalidTransition(Status current, Status target) {
200
+ return new DomainError(
201
+ ErrorCode.INVALID_TRANSITION,
202
+ "Cannot transition from " + current + " to " + target
203
+ );
204
+ }
205
+ }
206
+
207
+ // domain/error/ErrorCode.java
208
+
209
+ public enum ErrorCode {
210
+ NOT_FOUND,
211
+ INVALID_STATE,
212
+ VALIDATION,
213
+ DUPLICATE,
214
+ INVALID_TRANSITION
215
+ }
216
+ ```
217
+
218
+ ### Rules
219
+ - ✅ Use `RuntimeException` subclasses for domain errors
220
+ - ✅ Use `ErrorCode` enum for machine-readable error types
221
+ - ✅ Use `record` for value objects (Java 16+)
222
+ - ❌ Don't use Spring annotations in domain layer
223
+ - ❌ Don't use checked exceptions for domain errors
224
+
225
+ ---
226
+
227
+ ## 4. Application Layer
228
+
229
+ ```java
230
+ // application/service/EntityService.java
231
+
232
+ package com.project.module.application.service;
233
+
234
+ import com.project.module.domain.model.Entity;
235
+ import com.project.module.application.dto.Command;
236
+ import java.util.UUID;
237
+
238
+ public interface EntityService {
239
+ Entity execute(Command cmd);
240
+ Entity getById(UUID id);
241
+ }
242
+ ```
243
+
244
+ ```java
245
+ // application/impl/EntityServiceImpl.java
246
+
247
+ package com.project.module.application.impl;
248
+
249
+ import com.project.module.domain.model.Entity;
250
+ import com.project.module.domain.repository.EntityRepository;
251
+ import com.project.module.domain.error.DomainError;
252
+ import com.project.module.application.service.EntityService;
253
+ import com.project.module.application.dto.Command;
254
+ import lombok.RequiredArgsConstructor;
255
+ import lombok.extern.slf4j.Slf4j;
256
+ import org.springframework.stereotype.Service;
257
+ import org.springframework.transaction.annotation.Transactional;
258
+
259
+ @Slf4j
260
+ @Service
261
+ @RequiredArgsConstructor
262
+ public class EntityServiceImpl implements EntityService {
263
+
264
+ private final EntityRepository repository;
265
+
266
+ @Override
267
+ @Transactional
268
+ public Entity execute(Command cmd) {
269
+ var entity = new Entity();
270
+ entity.transition(cmd.desiredStatus());
271
+ repository.save(entity);
272
+ log.info("Entity executed: {}", entity.getId());
273
+ return entity;
274
+ }
275
+
276
+ @Override
277
+ @Transactional(readOnly = true)
278
+ public Entity getById(UUID id) {
279
+ return repository.findById(id)
280
+ .orElseThrow(() -> DomainError.notFound(id.toString()));
281
+ }
282
+ }
283
+ ```
284
+
285
+ ---
286
+
287
+ ## 5. Infrastructure Layer — JPA Repository
288
+
289
+ ```java
290
+ // infrastructure/repository/JpaEntityRepository.java
291
+
292
+ package com.project.module.infrastructure.repository;
293
+
294
+ import com.project.module.domain.model.Entity;
295
+ import com.project.module.domain.model.Status;
296
+ import com.project.module.domain.repository.EntityRepository;
297
+ import jakarta.persistence.EntityManager;
298
+ import lombok.RequiredArgsConstructor;
299
+ import org.springframework.stereotype.Repository;
300
+ import java.util.*;
301
+
302
+ @Repository
303
+ @RequiredArgsConstructor
304
+ public class JpaEntityRepository implements EntityRepository {
305
+
306
+ private final EntityManager em;
307
+
308
+ @Override
309
+ public Optional<Entity> findById(UUID id) {
310
+ return Optional.ofNullable(em.find(Entity.class, id));
311
+ }
312
+
313
+ @Override
314
+ public void save(Entity entity) {
315
+ em.persist(entity);
316
+ }
317
+
318
+ @Override
319
+ public void delete(UUID id) {
320
+ findById(id).ifPresent(em::remove);
321
+ }
322
+
323
+ @Override
324
+ public List<Entity> findByStatus(Status status) {
325
+ return em.createQuery("SELECT e FROM Entity e WHERE e.status = :status", Entity.class)
326
+ .setParameter("status", status)
327
+ .getResultList();
328
+ }
329
+ }
330
+ ```
331
+
332
+ ---
333
+
334
+ ## 6. Interfaces Layer — REST Controllers
335
+
336
+ ```java
337
+ // interfaces/rest/EntityController.java
338
+
339
+ package com.project.module.interfaces.rest;
340
+
341
+ import com.project.module.application.service.EntityService;
342
+ import com.project.module.application.dto.Command;
343
+ import com.project.module.interfaces.dto.CreateEntityRequest;
344
+ import com.project.module.interfaces.dto.EntityResponse;
345
+ import jakarta.validation.Valid;
346
+ import lombok.RequiredArgsConstructor;
347
+ import org.springframework.http.HttpStatus;
348
+ import org.springframework.web.bind.annotation.*;
349
+
350
+ @RestController
351
+ @RequestMapping("/api/v1/entities")
352
+ @RequiredArgsConstructor
353
+ public class EntityController {
354
+
355
+ private final EntityService service;
356
+
357
+ @PostMapping
358
+ @ResponseStatus(HttpStatus.CREATED)
359
+ public EntityResponse execute(@Valid @RequestBody CreateEntityRequest request) {
360
+ var entity = service.execute(request.toCommand());
361
+ return EntityResponse.from(entity);
362
+ }
363
+
364
+ @GetMapping("/{id}")
365
+ public EntityResponse getById(@PathVariable UUID id) {
366
+ var entity = service.getById(id);
367
+ return EntityResponse.from(entity);
368
+ }
369
+ }
370
+ ```
371
+
372
+ ### Global Exception Handler
373
+
374
+ ```java
375
+ // interfaces/advice/GlobalExceptionHandler.java
376
+
377
+ @RestControllerAdvice
378
+ public class GlobalExceptionHandler {
379
+
380
+ @ExceptionHandler(DomainError.class)
381
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
382
+ public ErrorResponse handleDomainError(DomainError e) {
383
+ return new ErrorResponse(e.getCode().name(), e.getMessage());
384
+ }
385
+
386
+ @ExceptionHandler(NotFoundException.class)
387
+ @ResponseStatus(HttpStatus.NOT_FOUND)
388
+ public ErrorResponse handleNotFound(NotFoundException e) {
389
+ return new ErrorResponse("NOT_FOUND", e.getMessage());
390
+ }
391
+
392
+ @ExceptionHandler(MethodArgumentNotValidException.class)
393
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
394
+ public ErrorResponse handleValidation(MethodArgumentNotValidException e) {
395
+ return new ErrorResponse("VALIDATION", "Invalid request body");
396
+ }
397
+ }
398
+ ```
399
+
400
+ ---
401
+
402
+ ## 7. Testing Patterns
403
+
404
+ ```java
405
+ @SpringBootTest
406
+ @AutoConfigureMockMvc
407
+ class EntityControllerTest {
408
+
409
+ @Autowired
410
+ private MockMvc mockMvc;
411
+
412
+ @MockBean
413
+ private EntityService service;
414
+
415
+ @Test
416
+ void shouldCreateEntity() throws Exception {
417
+ var entity = new Entity();
418
+ when(service.execute(any())).thenReturn(entity);
419
+
420
+ mockMvc.perform(post("/api/v1/entities")
421
+ .contentType(MediaType.APPLICATION_JSON)
422
+ .content("""
423
+ {"desiredStatus": "ACTIVE"}
424
+ """))
425
+ .andExpect(status().isCreated())
426
+ .andExpect(jsonPath("$.id").value(entity.getId().toString()));
427
+ }
428
+ }
429
+ ```
430
+
431
+ ---
432
+
433
+ ## 8. Anti-Patterns — NEVER DO
434
+
435
+ ```java
436
+ // ❌ Domain entities with Spring annotations
437
+ @Entity
438
+ @Table(name = "entities")
439
+ public class Entity { ... } // BAD — domain is JPA-free
440
+
441
+ // ❌ Business logic in controllers
442
+ @PostMapping
443
+ public void execute(@RequestBody Request req) {
444
+ // business logic here // BAD — use service layer
445
+ }
446
+
447
+ // ❌ Field injection
448
+ @Autowired
449
+ private EntityService service; // BAD — use constructor injection
450
+
451
+ // ❌ Mutable value objects without defensive copies
452
+ public class Money {
453
+ private int amount; // BAD — should be final
454
+ }
455
+
456
+ // ❌ Leaking JPA entities to interfaces
457
+ @Entity
458
+ public class User {
459
+ // used directly in REST responses // BAD — use DTOs
460
+ }
461
+ ```
462
+
463
+ ---
464
+
465
+ ## 9. Project-Level Structure
466
+
467
+ ```
468
+ src/main/java/com/company/project/
469
+ module/
470
+ domain/
471
+ model/
472
+ vo/
473
+ event/
474
+ repository/
475
+ error/
476
+ application/
477
+ service/
478
+ dto/
479
+ impl/
480
+ infrastructure/
481
+ repository/
482
+ persistence/
483
+ interfaces/
484
+ rest/
485
+ dto/
486
+ advice/
487
+ shared/
488
+ config/
489
+ logging/
490
+ src/test/java/com/company/project/
491
+ pom.xml (or build.gradle)
492
+ ```
493
+
494
+ ---
495
+
496
+ *Version: 1.0.0*
497
+ *Last updated: 2026-07-03*
498
+ *Source: Guardian DDD patterns + Spring Boot best practices + context7 (/jkazama/ddd-java, /websites/spring_io_spring-boot)*