Définition et rôle des interfaces applicatives
Une API (Application Programming Interface) désigne un ensemble de contrats normalisés permettant à des composants logiciels distincts d'interagir. En encapsulant la logique métier derrière des points d'entrée précis, elle autorise la consommation de services sans nécessité d'accéder au code source ni de maîtriser les mécanismes internes d'exécution.
Comparaison des modèles d'architecture Web
Les applications monolithiques traditionnelles déléguaient le rendu des vues au serveur. Cette approche imposait un couplage fort entre la couche présentation et la logique métier, générant une maintenance complexe, une latence accrue et une difficulté à paralléliser les équipes de développement.
Le paradigme moderne impose une stricte séparation : le backend expose exclusivement des contrats d'échange de données, tandis que le frontend gère l'interface utilisateur et l'état local. Cette découpe architecturale permet :
- Un cycle de vie de développement parallèle pour les équipes clientes et serveurs.
- Une réduction de la charge serveur grâce au rendu côté client et au chargement asynchrone.
- Une amélioration significative de la modularité, de la testabilité et de la maintenabilité du code.
Fondements du style REST
REST (Representational State Transfer) n'est pas un protocole mais une discipline architecturale exploitant les primitives natives d'HTTP. Elle traite l'information sous forme de ressources identifiées de manière unique par une URI. Les bonnes pratiques recommandent :
- L'utilisation exclusive de noms (généralement au pluriel) dans les chemins d'accès.
- La délégation de l'action à la méthode HTTP employée, éliminant ainsi les verbes dans l'URL.
- L'échange structuré via JSON, privilégié pour sa compatibilité native avec les écosystèmes JavaScript.
Mapping des opérations HTTP
| Verbe HTTP | Opération sémantique |
|---|---|
| GET | Extraction de données |
| POST | Instanciation d'une nouvelle ressource |
| PUT | Remplacement intégral d'une entité |
| PATCH | Modification partielle |
| DELETE | Suppression définitive |
Contraste URI : Approche classique vs REST
| Intention | Format classique | Format REST | Réception serveur |
|---|---|---|---|
| Liste complète | /item/fetchAll |
/items |
@GetMapping |
| Récupération unitaire | /item/get?id=5 |
/items/5 |
@GetMapping("/{identifiant}") |
| Création | /item/insert |
/items |
@PostMapping |
| Suppression | /item/remove?id=5 |
/items/5 |
@DeleteMapping("/{identifiant}") |
Remarques : L'encodage de paramètres complexes dans l'URL est déconseillé. Pour les données sensibles, l'approche par chemin masque les identifiants au niveau des logs d'accès standard.
Négociation de contenu et statut HTTP
La représentation des données s'orchestre via les en-têtes HTTP. Accept indique le format attendu par le consommateur, tandis que Content-Type spécifie le format des données transmises. Les réponses doivent refléter le résultat de l'opération :
| Code | Signification |
|---|---|
| 200 | Exécution validée |
| 201 | Création effectuée |
| 400 | Données d'entrée invalides |
| 401/403 | Problème d'authentification ou d'autorisation |
| 404 | Entité inexistante |
| 500 | Défaillance infrastructurelle |
Intégration avec l'écosystème Spring
Le framework Spring simplifie la mise en œuvre de ces standards. Depuis la version 2.2, la prise en charge native des méthodes PUT et DELETE nécessite l'activation du filtre dédié :
spring.mvc.hiddenmethod.filter.enabled=true
Les contrôleurs doivent être annotés avec @RestController pour sérialiser automatiquement les réponses en JSON. La capture des segments dynamiques d'URL s'effectue via @PathVariable. Les méthodes raccourcies (@GetMapping, @PostMapping, etc.) remplacent avantageusement la déclaration générique @RequestMapping(method = RequestMethod.X).
Implémentation complète
Interface cliente (JavaScript)
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Gestion des ressources</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
const BASE_URL = "/api/produits";
$("#chargerListe").on("click", () => {
$.get(BASE_URL, (data) => console.table(data));
});
$("#chercherSpecifique").on("click", () => {
$.get(BASE_URL + "/REF-789", (res) => console.log(res));
});
$("#enregistrer").on("click", () => {
$.ajax({
url: BASE_URL,
type: "POST",
contentType: "application/json",
data: JSON.stringify({nom: "Clavier mécanique", stock: 150}),
success: (retour) => console.log("Créé :", retour)
});
});
$("#modifierStock").on("click", () => {
$.ajax({
url: BASE_URL + "/REF-12",
type: "PUT",
contentType: "application/json",
data: JSON.stringify({nom: "Clavier gaming", stock: 45}),
success: () => console.log("Mise à jour terminée")
});
});
$("#retirerProduit").on("click", () => {
$.ajax({
url: BASE_URL + "/REF-42",
type: "DELETE",
success: (msg) => alert(msg.resultat)
});
});
});
</script>
</head>
<body>
<button id="chargerListe">Lister</button>
<button id="chercherSpecifique">Détail REF-789</button>
<button id="enregistrer">Créer</button>
<button id="modifierStock">Modifier REF-12</button>
<button id="retirerProduit">Supprimer REF-42</button>
</body>
</html>
Contrôleur Java (Spring Boot)
package fr.exposition.web;
import fr.exposition.domaine.Produit;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/api/produits")
public class GestionnaireProduits {
private final Map<String, Produit> catalogue = new ConcurrentHashMap<>();
public GestionnaireProduits() {
catalogue.put("REF-10", new Produit("REF-10", "Souris optique", 50));
catalogue.put("REF-22", new Produit("REF-22", "Tapis de souris", 200));
}
@GetMapping
public Collection<Produit> extraireInventaire() {
return catalogue.values();
}
@GetMapping("/{codeRef}")
public Optional<Produit> identifierArticle(@PathVariable String codeRef) {
return Optional.ofNullable(catalogue.get(codeRef));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Produit ajouterReference(@RequestBody Produit nouveau) {
nouveau.setReference("GEN-" + System.currentTimeMillis());
catalogue.put(nouveau.getReference(), nouveau);
return nouveau;
}
@PutMapping("/{codeRef}")
public Produit actualiserDetails(@PathVariable String codeRef, @RequestBody Produit modifie) {
Produit existant = catalogue.get(codeRef);
if (existant != null) {
existant.setDesignation(modifie.getDesignation());
existant.setQuantite(modifie.getQuantite());
return existant;
}
throw new IllegalArgumentException("Produit introuvable");
}
@DeleteMapping("/{codeRef}")
public Map<String, Object> supprimerReference(@PathVariable String codeRef) {
boolean retire = catalogue.remove(codeRef) != null;
return Map.of("action", "suppression", "reussite", retire);
}
}
Critères d'adoption
L'adoption d'une stratégie REST s'avère pertinente lorsque :
- Les entités du domaine possèdent des relations hiérarchiques ou associatives claires.
- Le système doit exposer des contrats publics destinés à des consommateurs tiers ou à des applications partenaires.
- L'équipe privilégie une séparation stricte entre la logique métier et l'interface utilisateur, avec une volonté de documenter les échanges via des standards ouverts.