Aller au contenu principal

Chapitre 4: Conception REST & Design Patterns

Objectif

Ces exercices couvrent la conception d'API REST et l'implémentation de design patterns — deux catégories de questions très fréquentes en entrevue backend Java/Spring Boot.


Exercice 1 — Concevoir un endpoint REST complet​

"Concevez et implémentez un endpoint pour créer et récupérer des utilisateurs."

Solution​

@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

@PostMapping
public ResponseEntity<UserDTO> create(@Valid @RequestBody CreateUserRequest req) {
UserDTO user = userService.create(req);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}

@GetMapping("/{id}")
public ResponseEntity<UserDTO> getById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}

@GetMapping
public ResponseEntity<Page<UserDTO>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(userService.findAll(PageRequest.of(page, size)));
}
}

Les DTOs (Data Transfer Objects)​

public record CreateUserRequest(
@NotBlank String name,
@Email String email
) {}

public record UserDTO(Long id, String name, String email) {}

Points clés à mentionner en entrevue​

Bonne pratiquePourquoi
DTOs au lieu d'entitésNe jamais exposer les entités JPA dans l'API
@ValidValider les entrées côté serveur
201 Created pour POSTCodes HTTP corrects
404 Not Found si absentRetourner le bon statut
Pagination sur les listesNe jamais retourner toutes les données d'un coup
Records Java pour les DTOsImmutables, concis, auto-génèrent equals/hashCode/toString
Phrase d'entrevue

"I use DTOs to avoid exposing entities, @Valid for input validation, proper HTTP status codes (201 Created, 404 Not Found), and pagination for list endpoints. I use Java Records for immutable DTOs."


Exercice 2 — Concevoir une API e-commerce​

"Concevez les endpoints REST pour un système de e-commerce."

Solution​

Products:
GET /api/products → Liste paginée des produits
GET /api/products/{id} → Détail d'un produit
POST /api/products → Créer un produit (ADMIN)
PUT /api/products/{id} → Modifier un produit (ADMIN)
DELETE /api/products/{id} → Supprimer un produit (ADMIN)

Orders:
POST /api/orders → Passer une commande
GET /api/orders → Mes commandes (utilisateur connecté)
GET /api/orders/{id} → Détail d'une commande
PATCH /api/orders/{id}/status → Modifier le statut (ADMIN)

Auth:
POST /api/auth/register → Inscription
POST /api/auth/login → Connexion (retourne JWT)
POST /api/auth/refresh → Rafraîchir le token

Cart:
GET /api/cart → Mon panier
POST /api/cart/items → Ajouter un article au panier
PUT /api/cart/items/{id} → Modifier la quantité
DELETE /api/cart/items/{id} → Retirer du panier

Bonnes pratiques REST mentionnées​

RègleExemple
Noms au pluriel/products pas /product
Verbes HTTP = actionsGET = lire, POST = créer, PUT = remplacer, PATCH = modifier partiellement, DELETE = supprimer
Pas de verbes dans l'URL/api/orders pas /api/getOrders
Ressources imbriquées/api/cart/items (items appartiennent au cart)
Sécurité par rôleADMIN pour les opérations sensibles

Exercice 3 — Implémenter le Builder Pattern​

"Créez une classe HttpRequest immuable avec le Builder Pattern."

Solution​

public final class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final String body;

private HttpRequest(Builder builder) {
this.url = Objects.requireNonNull(builder.url, "URL required");
this.method = builder.method;
this.headers = Map.copyOf(builder.headers); // copie défensive
this.body = builder.body;
}

public String getUrl() { return url; }
public String getMethod() { return method; }
public Map<String, String> getHeaders() { return headers; }
public String getBody() { return body; }

public static class Builder {
private String url;
private String method = "GET";
private final Map<String, String> headers = new HashMap<>();
private String body;

public Builder url(String url) { this.url = url; return this; }
public Builder method(String m) { this.method = m; return this; }
public Builder header(String k, String v) { headers.put(k, v); return this; }
public Builder body(String b) { this.body = b; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
}

Utilisation​

HttpRequest req = new HttpRequest.Builder()
.url("https://api.sonar.com/issues")
.method("POST")
.header("Authorization", "Bearer token123")
.header("Content-Type", "application/json")
.body("{\"severity\": \"CRITICAL\"}")
.build();

Points clés​

TechniquePourquoi
Classe finalEmpêche l'héritage
Champs finalImmutabilité
Constructeur privateForcer l'utilisation du Builder
Map.copyOf()Copie défensive — l'objet ne peut pas être modifié après création
Objects.requireNonNull()Validation des champs obligatoires
Fluent API (return this)Chaînage des appels
Phrase d'entrevue

"Immutable object, defensive copies for collections, required fields validated in constructor. The fluent API makes the call site self-documenting."


Exercice 4 — Implémenter le Singleton Pattern (Thread-Safe)​

"Implémentez un Singleton thread-safe en Java."

Solution moderne (Enum Singleton)​

public enum DatabaseConnection {
INSTANCE;

private final Connection connection;

DatabaseConnection() {
this.connection = createConnection();
}

public Connection getConnection() {
return connection;
}

private Connection createConnection() {
// Logique de connexion
return DriverManager.getConnection("jdbc:postgresql://localhost/mydb");
}
}

// Utilisation
Connection conn = DatabaseConnection.INSTANCE.getConnection();

Solution classique (Double-Checked Locking)​

public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private final Connection connection;

private DatabaseConnection() {
this.connection = createConnection();
}

public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
}
Phrase d'entrevue

"I prefer the enum approach — it's the simplest, thread-safe by default, and handles serialization. Joshua Bloch recommends it in Effective Java."

Note Spring Boot

En Spring Boot, le Singleton Pattern est rarement implémenté manuellement. Les beans Spring sont singleton par défaut dans le conteneur IoC. Si l'intervieweur pose la question, mentionnez que vous utilisez @Service ou @Component qui sont singleton par défaut.


Exercice 5 — Observer Pattern avec Spring Events​

"Implémentez un système de notification découplé."

Solution avec Spring Events​

// L'événement
public class OrderCreatedEvent {
private final Order order;
public OrderCreatedEvent(Order order) { this.order = order; }
public Order getOrder() { return order; }
}

// Le publisher (Service qui crée la commande)
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;

@Transactional
public Order createOrder(OrderRequest req) {
Order order = orderRepository.save(new Order(req));
eventPublisher.publishEvent(new OrderCreatedEvent(order));
return order;
}
}

// Les listeners (réagissent à l'événement)
@Component
class EmailNotificationListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
emailService.sendConfirmation(event.getOrder());
}
}

@Component
class InventoryListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
inventoryService.decrementStock(event.getOrder().getItems());
}
}

@Component
class AnalyticsListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent event) {
analyticsService.trackOrder(event.getOrder());
}
}

Avantages​

Sans ObserverAvec Observer
OrderService connaît EmailService, InventoryService, AnalyticsService...OrderService publie un événement, c'est tout
Ajouter un listener = modifier OrderServiceAjouter un listener = créer une classe, zéro modification
Couplage fortDécouplage total
Phrase d'entrevue

"I use Spring Events for the Observer Pattern. The OrderService just publishes an event — it doesn't know who listens. Adding a new reaction is just creating a new @EventListener class."


Résumé des Design Patterns​

PatternProblème résoluExemple Spring
StrategyVarier un algorithmeFeeCalculator avec Map
BuilderConstruire des objets complexesHttpRequest.Builder
SingletonInstance uniqueBeans Spring (par défaut)
ObserverNotifications découplées@EventListener
FactoryCréer des objets sans connaître le typeBeanFactory
Template MethodSquelette d'algorithmeJdbcTemplate, RestTemplate
ProxyIntercepion transparente@Transactional, @Cacheable (proxies AOP)

⬅️ Chapitre 3: Algorithmes | Chapitre 5: Débogage & Revue de code →