HTTP → Controller → Service → Repository → MongoDB ↕ DTO (entrée/sortie) Entity (@Document)On code de l'intérieur vers l'extérieur : d'abord ce qui touche la base (entité, repository), puis la logique (service), enfin l'exposition (contrôleur).
Créer le projet Spring Boot
Générer un projet propre avec les bonnes dépendances, avant d'écrire la moindre classe.
Sur Spring Initializr, projet Maven / Java 21, puis on ajoute les dépendances :
- Spring Web — les contrôleurs REST
- Spring Data MongoDB — l'accès à la base
- Validation —
@Valid,@NotNull… - Lombok (optionnel) — moins de code répétitif
# application.properties
spring.data.mongodb.uri=mongodb://localhost:27017/mediatheque
- Génère un projet
fioul-apiavec les mêmes dépendances + springdoc-openapi (étape 7). - Configure l'URI vers ta base
fioul(locale, puis Atlas plus tard). - Ajoute le projet dans le dépôt
fioul-groupe(dossierapi/). - Vérifie que l'application démarre (
mvn spring-boot:run).
L'entité — le reflet du document
Une classe annotée @Document représente une collection MongoDB. C'est le miroir Java du document modélisé au parcours 1.
@Document(collection = "livres") public class LivreEntity { @Id private String id; private String titre; private String isbn; private int anneePublication; private String auteurId; // référence private List<Avis> avis; // embarqué // getters / setters }
- Crée
CampagneEntityannotée@Document("campagnes"). - Champs :
id,zone,statut(enumStatut),dateLimite,fournisseurId. - Ajoute
List<Commande> commandes(classe embarquée aveclitres,commune,adresse). - Crée
FournisseurEntityavec sa liste dePalier.
List<Commande> embarquée, fournisseurId référencé.Le repository — l'accès aux données
Une interface qui étend MongoRepository fournit le CRUD gratuitement. On y ajoute des méthodes dérivées dont Spring devine l'implémentation d'après le nom.
public interface LivreRepository extends MongoRepository<LivreEntity, String> { List<LivreEntity> findByAnneePublicationGreaterThan(int annee); Optional<LivreEntity> findByIsbn(String isbn); }
findAll(), findById(), save(), deleteById() existent déjà, sans code.
- Crée
CampagneRepository extends MongoRepository<CampagneEntity, String>. - Ajoute
findByStatut(Statut statut). - Ajoute
findByZoneAndStatut(String zone, Statut statut). - Crée aussi
FournisseurRepository.
findByZoneAndStatut produit exactement la requête que tu as écrite à la main en mongosh (étape 5).Le DTO — ce que voit le client
On n'expose jamais l'entité directement. Un DTO (Data Transfer Object) définit précisément ce qui entre et ce qui sort de l'API : on masque certains champs, on en calcule d'autres.
public record LivreDTO( String id, String titre, String isbn, double noteMoyenne, // calculé, pas stocké int nbAvis ) {}
Le record Java est parfait pour un DTO : immuable et concis.
- Crée un
record CampagneDTOexposé en lecture. - Champs stockés :
id,zone,statut,dateLimite. - Champs calculés :
volumeTotaletprixLitre(l'agrégation du parcours 1 !). - Crée un
CreerCommandeDTO(entrée) :litres,commune,adresse.
fournisseurId ni la liste brute des commandes ; il veut le volume et le prix. Le DTO fait ce tri.Le service — la logique métier
Le service orchestre : il appelle le repository, applique les règles métier et transforme les entités en DTO. C'est le cerveau, entre le contrôleur et la base.
@Service public class LivreService { private final LivreRepository repo; // injection par constructeur public LivreService(LivreRepository r){ this.repo=r; } public List<LivreDTO> lister(){ return repo.findAll().stream() .map(this::versDTO).toList(); } private LivreDTO versDTO(LivreEntity e){ double moy = e.getAvis().stream() .mapToInt(Avis::getNote).average().orElse(0); return new LivreDTO(e.getId(), e.getTitre(), e.getIsbn(), moy, e.getAvis().size()); } }
- Crée
CampagneServiceavec injection par constructeur. listerOuvertes(zone)→ appelle le repository, mappe en DTO.rejoindre(id, CreerCommandeDTO)→ ajoute une commande (le$pushde l'étape 4 MongoDB).- Écris
calculerPrix(volume, fournisseur)qui choisit le bon palier. - Règle métier : refuser une inscription si la campagne est
CLOTUREEou la date dépassée.
Le contrôleur — la porte d'entrée REST
Le contrôleur relie les URLs HTTP aux méthodes du service. Il ne contient aucune logique métier : il reçoit, délègue, renvoie.
@RestController @RequestMapping("/api/livres") public class LivreController { private final LivreService service; public LivreController(LivreService s){ this.service=s; } @GetMapping public List<LivreDTO> lister(){ return service.lister(); } @GetMapping("/{id}") public LivreDTO consulter(@PathVariable String id){ return service.consulter(id); } }
Implémente les endpoints du projet :
GET /api/campagnes(filtre?zone=)GET /api/campagnes/{id}POST /api/campagnes(ouvrir)POST /api/campagnes/{id}/commandes(rejoindre)PUT /api/campagnes/{id}/cloturer
curl, vérifie chaque endpoint avant de passer à Flutter.Valider les entrées
On ne fait jamais confiance aux données reçues. Les annotations de validation (@NotBlank, @Min…) + @Valid rejettent proprement les requêtes invalides.
public record CreerAvisDTO( @NotBlank String pseudo, @Min(0) @Max(5) int note, @Size(max=500) String commentaire ) {} // dans le contrôleur : public ... ajouter(@Valid @RequestBody CreerAvisDTO dto){ ... }
@Min(500)surlitres(quantité minimale de commande).@NotBlanksurcommuneetadresse.- Ajoute un
@RestControllerAdvicequi renvoie un 400 lisible en cas d'erreur. - Règle métier (dans le service) : renvoyer 409 si la campagne est clôturée.
Documenter avec Swagger / OpenAPI
Une seule dépendance déploie une interface web qui documente et teste l'API. On enrichit avec quelques annotations.
<!-- pom.xml -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
Sans configuration :
- UI sur
/swagger-ui/index.html - Description JSON sur
/v3/api-docs
@Tag(name = "Livres") @RestController public class LivreController { ... }
- Ajoute la dépendance et lance Swagger UI.
- Annote tes contrôleurs avec
@Taget@Operation(summary=...). - Teste
POST /campagnes/{id}/commandesdepuis « Try it out ». - Capture d'écran de Swagger → dans le README du projet.