Contactez-nous

Gestion des versions d'API

Explorez différentes stratégies pour versionner vos API REST Spring Boot (URI, paramètre, en-tête, media type) afin de gérer l'évolution sans casser les clients existants.

Pourquoi versionner vos API REST ?

Les API, comme tout logiciel, évoluent avec le temps. De nouvelles fonctionnalités sont ajoutées, des structures de données changent, des comportements sont modifiés. Cependant, une API REST est un contrat entre le serveur et ses clients. Introduire des changements incompatibles (breaking changes) dans une API existante peut casser les applications clientes qui dépendent de l'ancienne version du contrat. Pour éviter cela et permettre une évolution maîtrisée, il est essentiel de mettre en place une stratégie de gestion des versions d'API.

Le versioning permet aux clients de continuer à utiliser une version spécifique de l'API avec laquelle ils sont compatibles, même si des versions plus récentes avec des changements majeurs sont déployées. Cela garantit la rétrocompatibilité (backward compatibility) et offre une transition en douceur pour les clients qui souhaitent migrer vers les nouvelles versions. Une bonne stratégie de versioning communique clairement l'évolution de l'API et gère le cycle de vie des différentes versions (introduction, dépréciation, retrait).

Stratégies communes de versioning d'API

Il existe plusieurs approches populaires pour indiquer la version d'une API REST qu'un client souhaite utiliser. Chacune a ses avantages et ses inconvénients. Voyons comment les implémenter dans Spring Boot.

1. Versioning par chemin d'URI (URI Path Versioning)

C'est l'une des méthodes les plus courantes et les plus explicites. La version de l'API est incluse directement dans le chemin de l'URL.

  • Format : /api/v1/ressources, /api/v2/ressources
  • Exemple de requête : GET /api/v1/products/123
  • Implémentation Spring Boot : Utilisez l'annotation @RequestMapping (ou ses dérivées comme @GetMapping) au niveau de la classe du contrôleur ou au niveau de la méthode pour inclure le préfixe de version.
// Contrôleur pour la V1
@RestController
@RequestMapping("/api/v1/products")
public class ProductControllerV1 {
    @GetMapping("/{id}")
    public String getProductV1(@PathVariable Long id) {
        return "Récupération du produit (V1) ID: " + id;
    }
    // ... autres méthodes V1 ...
}

// Contrôleur distinct pour la V2
@RestController
@RequestMapping("/api/v2/products")
public class ProductControllerV2 {
    @GetMapping("/{id}")
    public ProductDtoV2 getProductV2(@PathVariable Long id) {
        // Retourne une structure différente pour V2
        return new ProductDtoV2(id, "Produit V2", 99.99, "NOUVELLE_CATEGORIE");
    }
    // ... autres méthodes V2 ...
}

// DTO pour V2 (exemple)
class ProductDtoV2 {
    public Long productId; public String productName; public Double price; public String categoryCode;
    public ProductDtoV2(Long id, String n, Double p, String c) { this.productId=id; this.productName=n; this.price=p; this.categoryCode=c; }
}
  • Avantages : Très explicite et visible. Facile à router par les serveurs web et les proxys. Facile à explorer avec un navigateur.
  • Inconvénients : Certains puristes REST argumentent que l'URI doit identifier la ressource, pas sa représentation versionnée. Peut conduire à une prolifération des URIs si de nombreuses versions coexistent.

2. Versioning par paramètre de requête (Query Parameter Versioning)

La version est spécifiée via un paramètre dans la chaîne de requête de l'URL.

  • Format : /api/ressources?version=1, /api/ressources?api-version=2
  • Exemple de requête : GET /api/products/123?version=1
  • Implémentation Spring Boot : Vous pouvez utiliser l'attribut params des annotations de mapping ou vérifier la valeur d'un @RequestParam dans votre méthode.
@RestController
@RequestMapping("/api/products") // URI de base sans version
public class ProductControllerParamVersion {

    // Méthode pour V1 via l'attribut params
    @GetMapping(value = "/{id}", params = "version=1")
    public String getProductV1(@PathVariable Long id) {
        return "Récupération param V1 ID: " + id;
    }

    // Méthode pour V2 via l'attribut params
    @GetMapping(value = "/{id}", params = "version=2")
    public ProductDtoV2 getProductV2(@PathVariable Long id) {
        return new ProductDtoV2(id, "Produit Param V2", 101.50, "CAT_V2");
    }

    // Alternative V1: vérification manuelle (moins élégant)
    // @GetMapping("/{id}")
    // public Object getProductByParam(@PathVariable Long id, @RequestParam(defaultValue = "1") String version) {
    //     if ("2".equals(version)) {
    //         return new ProductDtoV2(id, "Produit Param V2", 101.50, "CAT_V2");
    //     } else {
    //         return "Récupération param V1 ID: " + id;
    //     }
    // }
}
  • Avantages : Garde les URIs propres. Facile à implémenter.
  • Inconvénients : Moins explicite que le versioning par URI. Les paramètres de requête sont souvent considérés comme servant à filtrer ou paginer, pas à versionner. Le routage et le cache peuvent être légèrement plus complexes à configurer.

3. Versioning par en-tête personnalisé (Custom Header Versioning)

La version est spécifiée dans un en-tête HTTP personnalisé.

  • Format : En-tête X-API-Version: 1 ou Api-Version: 2
  • Exemple de requête : GET /api/products/123 avec l'en-tête X-API-Version: 1
  • Implémentation Spring Boot : Utilisez l'attribut headers des annotations de mapping.
@RestController
@RequestMapping("/api/products")
public class ProductControllerHeaderVersion {

    // Méthode pour V1 via l'attribut headers
    @GetMapping(value = "/{id}", headers = "X-API-Version=1")
    public String getProductV1(@PathVariable Long id) {
        return "Récupération header V1 ID: " + id;
    }

    // Méthode pour V2 via l'attribut headers
    @GetMapping(value = "/{id}", headers = "X-API-Version=2")
    public ProductDtoV2 getProductV2(@PathVariable Long id) {
        return new ProductDtoV2(id, "Produit Header V2", 222.0, "CAT_HEADER_V2");
    }
}
  • Avantages : Garde les URIs propres et indépendantes de la version. N'encombre pas les paramètres de requête.
  • Inconvénients : Moins visible ou découvrable que le versioning par URI. Ne peut pas être testé simplement via la barre d'adresse d'un navigateur. Nécessite que les clients configurent correctement les en-têtes.

4. Versioning par type de média (Media Type Versioning / Content Negotiation)

Cette approche utilise l'en-tête HTTP standard Accept pour demander une version spécifique via un type de média personnalisé.

  • Format : En-tête Accept: application/vnd.company.app-v1+json
  • Exemple de requête : GET /api/products/123 avec l'en-tête Accept: application/vnd.company.app-v1+json
  • Implémentation Spring Boot : Utilisez l'attribut produces des annotations de mapping pour spécifier le type de média que la méthode peut générer.
@RestController
@RequestMapping("/api/products")
public class ProductControllerMediaTypeVersion {

    // Méthode pour V1 via l'attribut produces
    @GetMapping(value = "/{id}", produces = "application/vnd.certiquizz.api-v1+json")
    public String getProductV1(@PathVariable Long id) {
        // Pourrait retourner un DTO V1 spécifique sérialisé en JSON
        return "{\"version\":\"media-type-v1\", \"productId\":\""+id+"\"}";
    }

    // Méthode pour V2 via l'attribut produces
    @GetMapping(value = "/{id}", produces = "application/vnd.certiquizz.api-v2+json")
    public ProductDtoV2 getProductV2(@PathVariable Long id) {
        return new ProductDtoV2(id, "Produit Media Type V2", 350.75, "CAT_MEDIA_V2");
    }
}
  • Avantages : Considéré par beaucoup comme l'approche la plus "RESTful" car elle utilise les mécanismes HTTP standards (négociation de contenu). Garde les URIs propres.
  • Inconvénients : Plus complexe à comprendre et à mettre en oeuvre. Les types de médias personnalisés peuvent devenir complexes. Moins intuitif pour les consommateurs d'API. Les en-têtes `Accept` peuvent devenir longs.

Choisir une stratégie et bonnes pratiques

Il n'y a pas de "meilleure" stratégie universelle ; le choix dépend du contexte de votre projet, de votre équipe et des consommateurs de votre API. Le versioning par URI est souvent choisi pour sa simplicité et sa clarté, malgré les critiques sur la pureté REST. Le versioning par en-tête est une alternative populaire qui garde les URIs propres.

Quelques bonnes pratiques à considérer :

  • Etre cohérent : Choisissez une stratégie et appliquez-la de manière cohérente sur toute votre API.
  • Documentation claire : Documentez clairement les versions disponibles, les changements entre les versions et la stratégie de versioning utilisée (OpenAPI/Swagger est idéal pour cela).
  • Plan de dépréciation : Ayez une politique claire sur la durée de vie des anciennes versions et communiquez à l'avance lorsque vous prévoyez de déprécier ou de supprimer une version.
  • Version par défaut : Envisagez de traiter les requêtes sans version spécifiée comme ciblant une version par défaut (souvent la dernière version stable).
  • Préfixe global : Utiliser un préfixe comme /api/ avant la version peut aider à distinguer les endpoints d'API d'autres ressources web.

La gestion des versions est un aspect crucial de la maintenance et de l'évolution des API REST. En choisissant une stratégie appropriée et en l'implémentant correctement avec Spring Boot, vous assurez la stabilité pour vos clients tout en vous donnant la flexibilité nécessaire pour innover.