SKILL
v1.2.0
java-backend
Développement Java/Spring Boot selon les préconisations DDD hexagonal, Spring Boot 4, Java 25. Détecte automatiquement le type de projet et charge les spécificités correspondantes.
Category
Development
Author
Mathieu Durand
Version
1.2.0
Components
bolt
java-backend
Skill
java-backend
Skill
java-backend
Développement Java/Spring Boot selon les préconisations Additi (DDD hexagonal, Spring Boot 4, Java 25).
Usage
Ce skill détecte automatiquement le type de projet (API Web ou Worker) et charge les spécificités correspondantes.
/java-backend <description de la tâche>
Ressources complémentaires :
- API Web (WebMVC) : Voir
references/application-web.mdpour les spécificités REST - Persistance JPA : Voir
references/infrastructure-jpa.mdpour JPA/Spring Data et Testcontainers
Principes appliqués
- Architecture hexagonale : séparation stricte domain / application / infrastructure
- DDD : agrégats, value objects, domain events
- Spring Boot 4 : configuration par convention, actuator, observability
- Java 25 : records, sealed classes, pattern matching
Structure de projet attendue
src/
main/
java/
com.example/
domain/ # Entités, value objects, ports
application/ # Use cases, command handlers
infrastructure/ # Adapters, repositories, config
test/
java/
com.example/
domain/ # Tests unitaires du domaine
application/ # Tests d'intégration use cases
Conventions
- Les ports sont des interfaces dans le domaine
- Les adapters implémentent les ports dans l'infrastructure
- Les use cases ne dépendent que du domaine
- Tests unitaires sans Spring context pour le domaine
description Ressources référencées
references/application-web.md
Application Web (WebMVC)
Spécificités pour les projets exposant une API REST avec Spring WebMVC.
Structure application/
application/
└── rest/
├── <Resource>Controller.java # @RestController
├── <Resource>Request.java # record (validation @jakarta)
└── <Resource>Response.java # record
Controller
@RestController
@RequestMapping("/api/v1/commandes")
@Validated
public class CommandeController {
private final CommandeService commandeService;
public CommandeController(CommandeService commandeService) {
this.commandeService = commandeService;
}
@GetMapping("/{id}")
public CommandeResponse getById(@PathVariable UUID id) {
return commandeService.findById(id)
.map(CommandeResponse::from)
.orElseThrow(() -> new CommandeNotFoundException(id));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public CommandeResponse create(@RequestBody @Valid CommandeRequest request) {
return CommandeResponse.from(commandeService.create(request.toDomain()));
}
}
Gestion des erreurs
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CommandeNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ProblemDetail handleNotFound(CommandeNotFoundException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
final var detail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
detail.setProperty("errors", ex.getBindingResult().getFieldErrors()
.stream().map(e -> e.getField() + ": " + e.getDefaultMessage()).toList());
return detail;
}
}
Configuration actuator
management:
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
health:
show-details: always
references/infrastructure-jpa.md
Infrastructure JPA / PostgreSQL
Spécificités pour la persistance avec Spring Data JPA et PostgreSQL.
Dépendances Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
Configuration
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: ${DB_USER}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
open-in-view: false
flyway:
locations: classpath:db/migration
Entité JPA
@Entity
@Table(name = "commandes")
public class CommandeEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(nullable = false)
private String reference;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private StatutCommande statut;
// Getters, setters ou record pattern
}
Repository
// Interface dans le domain
public interface CommandeRepository {
Optional<Commande> findById(UUID id);
Commande save(Commande commande);
}
// Implémentation dans l'infrastructure
@Repository
public class CommandeJpaRepository implements CommandeRepository {
private final CommandeJpaSpringRepository springRepository;
private final CommandeMapper mapper;
@Override
public Optional<Commande> findById(UUID id) {
return springRepository.findById(id).map(mapper::toDomain);
}
}
Tests avec Testcontainers
@SpringBootTest
@Testcontainers
class CommandeJpaRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}