Bonjour ! Dans la leçon précédente, nous avons jeté les bases de notre projet e-commerce en modélisant les relations entre les entités Product, Order, et OrderDetail avec JPA. Vous avez appris à utiliser @OneToMany et @ManyToOne pour construire une structure de données relationnelle robuste.
Aujourd'hui, nous allons donner vie à ce modèle. L'objectif est d'implémenter une des logiques métier les plus critiques d'une application e-commerce : le passage d'une commande. Cette opération est considérée comme "complexe" car elle ne se résume pas à une simple insertion dans une table. Elle implique la création d'une commande, la création de plusieurs lignes de commande, la validation du stock, et la mise à jour des quantités de produits, le tout de manière cohérente. C'est le cœur de la couche service de notre application.
1. La Couche Service : Le Cerveau de l'Application
Votre expérience en développement web vous a déjà familiarisé avec l'idée de séparer les responsabilités. En backend Java avec Spring, l'architecture la plus courante est l'architecture en couches, et plus spécifiquement le pattern Controller - Service - Repository.
Pour bien comprendre le rôle de chaque couche, je vous invite à lire une courte introduction sur ce pattern.
3- [Spring-boot] Du code, comme on aime | by Mamoudou Diallo
Cet article de Mamoudou Diallo sur Medium explique de manière claire et concise les responsabilités de chaque couche.
Concentrez-vous sur la section "Qu’est-ce que ce pattern ?". Portez attention à la description des rôles du Controller (gestion des requêtes HTTP), du Repository (persistance des données), et surtout du Service (logique métier).
Pour résumer et visualiser cette structure :
- Controller : La porte d'entrée. Il reçoit la requête HTTP, extrait les données (DTOs) et délègue le travail au service. Il ne contient aucune logique métier.
- Service : Le chef d'orchestre. C'est ici que résident les règles et les processus métier. Un service peut appeler un ou plusieurs repositories pour accomplir une tâche.
- Repository : L'expert en données. Il communique directement avec la base de données pour effectuer les opérations CRUD.
Le diagramme suivant illustre parfaitement cette organisation. Notre logique de commande appartiendra à la couche "Business (Métier)".

La vidéo ci-dessous explique très bien cette séparation des rôles et pourquoi la logique métier ne doit pas être dans le contrôleur.
Apprendre Spring Boot #2 HTTP POST & DTO : Controller → Service → JPA (RequestBody)
Cette vidéo de JirAWS, bien que focalisée sur une requête POST, commence par une excellente explication de l'architecture en couches.
Regardez la section entre la couche métier. L'auteur y définit ce qu'est la "logique métier" avec des exemples très concrets, comme la validation d'un mot de passe ou l'annulation d'une commande en cas d'échec de paiement. C'est exactement ce que nous allons faire.
2. Garantir la Cohérence avec @Transactional
Imaginez le processus de création de commande :
- Enregistrer la nouvelle commande dans la table
orders. - Pour chaque produit, enregistrer une ligne dans la table
order_details. - Pour chaque produit, décrémenter le stock dans la table
products.
Que se passe-t-il si l'application plante après l'étape 1 mais avant l'étape 3 ? Vous auriez une commande enregistrée, mais le stock ne serait pas à jour. Vos données seraient incohérentes.
La solution à ce problème est le concept de transaction. Une transaction est une opération "tout ou rien" (atomique). Soit toutes les étapes réussissent, soit, en cas d'erreur, toutes les modifications déjà effectuées sont annulées (on parle de rollback).
Spring simplifie énormément la gestion des transactions avec l'annotation @Transactional.
Comprendre appliquer @Transactional au niveau du service
Cet article de StudyRaid est une excellente ressource qui va droit au but sur l'importance de @Transactional dans la couche service.
Lisez les sections "Pourquoi appliquer @Transactional au niveau du service ?" et "Comment fonctionne @Transactional dans un service ?". Elles expliquent parfaitement comment Spring gère la transaction pour vous : il la démarre avant l'appel de la méthode et la valide (commit) si tout se passe bien, ou l'annule (rollback) si une exception est levée.
En plaçant @Transactional sur notre méthode de service createOrder(), nous nous assurons que la création de la commande, de ses détails et la mise à jour du stock forment une seule et unique opération indivisible.
3. Implémentation de la Logique de Commande
Passons maintenant au code. Pour nous concentrer sur la couche service, je vais supposer que les étapes suivantes sont déjà réalisées :
-
Repositories : Des interfaces
OrderRepository,ProductRepository, etOrderDetailRepositoryont été créées, chacune étendantJpaRepository. -
DTOs : Nous avons des DTOs pour la requête. Le client enverra un JSON qui ressemblera à ceci, mappé sur des classes Java
CreateOrderRequestetOrderItemRequest.{ "customerName": "John Doe", "items": [ { "productId": 1, "quantity": 2 }, { "productId": 3, "quantity": 1 } ] } -
Entités : L'entité
ProductEntitya maintenant un champprivate int stock;.
Voici à quoi ressemblerait notre OrderService.
// package com.example.ecommerce.service;
import com.example.ecommerce.dto.CreateOrderRequest;
import com.example.ecommerce.dto.OrderItemRequest;
import com.example.ecommerce.entity.OrderEntity;
import com.example.ecommerce.entity.OrderDetailEntity;
import com.example.ecommerce.entity.ProductEntity;
import com.example.ecommerce.repository.OrderDetailRepository;
import com.example.ecommerce.repository.OrderRepository;
import com.example.ecommerce.repository.ProductRepository;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
private final OrderDetailRepository orderDetailRepository;
public OrderService(OrderRepository orderRepository, ProductRepository productRepository, OrderDetailRepository orderDetailRepository) {
this.orderRepository = orderRepository;
this.productRepository = productRepository;
this.orderDetailRepository = orderDetailRepository;
}
@Transactional // Annotation clé !
public OrderEntity createOrder(CreateOrderRequest request) {
// 1. Créer l'entité de commande principale
OrderEntity order = new OrderEntity();
order.setCustomerName(request.getCustomerName());
order.setOrderDate(LocalDateTime.now());
OrderEntity savedOrder = orderRepository.save(order);
// 2. Traiter chaque article de la commande
for (OrderItemRequest itemRequest : request.getItems()) {
// Règle métier 1 : Vérifier que le produit existe
ProductEntity product = productRepository.findById(itemRequest.getProductId())
.orElseThrow(() -> new EntityNotFoundException("Product not found with id: " + itemRequest.getProductId()));
// Règle métier 2 : Vérifier le stock
if (product.getStock() < itemRequest.getQuantity()) {
throw new IllegalStateException("Insufficient stock for product: " + product.getName());
}
// 3. Créer l'entité de détail de commande
OrderDetailEntity orderDetail = new OrderDetailEntity();
orderDetail.setOrder(savedOrder);
orderDetail.setProduct(product);
orderDetail.setQuantity(itemRequest.getQuantity());
orderDetail.setUnitPrice(product.getPrice()); // On "photographie" le prix au moment de l'achat
orderDetailRepository.save(orderDetail);
// Règle métier 3 : Mettre à jour le stock du produit
product.setStock(product.getStock() - itemRequest.getQuantity());
productRepository.save(product);
}
return savedOrder;
}
}
Analysons cette méthode :
- Elle est annotée avec
@Transactional. Si uneEntityNotFoundExceptionou uneIllegalStateExceptionest levée, Spring s'assurera que la commande (savedOrder) et tous lesorderDetaildéjà sauvegardés soient supprimés de la base de données. La transaction est entièrement annulée. - Elle orchestre des appels à trois repositories différents. C'est typique d'une logique métier complexe.
- Elle implémente plusieurs règles métier :
- La validation de l'existence du produit.
- La vérification du stock disponible.
- La mise à jour du stock.
- La sauvegarde du prix au moment de l'achat (
unitPrice), une règle cruciale pour que l'historique des commandes soit correct même si les prix des produits changent plus tard.
L'exemple de code dans la ressource StudyRaid que vous avez lue est très similaire et renforce cette approche. Il est bon de voir le même pattern appliqué dans différents contextes.
Comprendre appliquer @Transactional au niveau du service
Revenez sur l'article de StudyRaid.
Lisez la section "Exemple concret : Service transactionnel complet". Vous y retrouverez exactement la même structure : une méthode de service annotée @Transactional qui orchestre des opérations sur un OrderRepository et un ProductRepository.
4. Le Rôle Simplifié du Controller
Maintenant que toute la logique est dans le service, à quoi ressemble le contrôleur ? Il devient remarquablement simple.
// package com.example.ecommerce.controller;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public ResponseEntity<OrderDto> createOrder(@RequestBody CreateOrderRequest request) {
OrderEntity createdOrder = orderService.createOrder(request);
// On pourrait mapper 'createdOrder' vers un OrderDto avant de le renvoyer
// Pour l'instant, nous simplifions
return ResponseEntity.status(HttpStatus.CREATED).body(convertToDto(createdOrder));
}
// ... méthode privée convertToDto non montrée ...
}
Le contrôleur ne fait que trois choses :
- Recevoir la requête HTTP et désérialiser le JSON en
CreateOrderRequest. - Appeler la méthode
createOrderdu service. - Renvoyer une réponse HTTP
201 Createdavec les détails de la commande créée.
Il est complètement découplé de la complexité de la logique métier. C'est propre, maintenable et facile à tester.
Conclusion
Dans cette leçon, vous avez franchi une étape essentielle pour devenir un développeur backend professionnel. Vous avez déplacé la logique de l'application dans la couche de service, là où elle doit être, et vous avez appris à utiliser @Transactional pour garantir l'intégrité de vos données lors d'opérations complexes.
Points clés à retenir :
- La logique métier (règles, validations, orchestration) appartient à la couche service.
- L'architecture Controller-Service-Repository permet de séparer clairement les responsabilités.
- L'annotation
@Transactionalest indispensable pour les opérations qui modifient plusieurs entités, car elle garantit l'atomicité (tout ou rien). - En cas d'erreur (exception non capturée) dans une méthode transactionnelle, toutes les modifications de la transaction sont annulées (rollback).
Exercice de réflexion :
Imaginez que nous ajoutions une nouvelle règle métier : "Un client ne peut pas commander plus de 5 unités du même produit dans une seule commande". Dans quelle classe et dans quelle méthode ajouteriez-vous cette vérification ?
Indice : La réponse se trouve dans le code de la méthode que nous venons d'écrire.
Dans la prochaine leçon, nous aborderons un sujet fondamental pour toute application exposée sur le web : la sécurité. Vous apprendrez à configurer Spring Security pour protéger les endpoints de notre API, en commençant par une authentification basique.