Versionner une API sans la casser pendant cinq ans
Comment empiler des couches de compatibilité au-dessus d'un seul modèle courant, ce que chaque couche coûte pour toujours, et les changements qu'aucune couche ne sait absorber.
Par Elias Varen7 min de lectureMembres
/v2 est une promesse que vous ne tiendrez pas. Le jour où vous l'ouvrez, vous avez deux implémentations à maintenir. Les intégrateurs de /v1 ne migrent pas, parce que leur intégration fonctionne et que personne chez eux n'a de budget pour la refaire, et le premier correctif de sécurité doit être écrit deux fois. Deux ans plus tard, /v3 s'ajoute, et la moitié de votre code d'API sert des clients que vous n'osez pas couper.
Tenir cinq ans demande l'inverse : une seule implémentation, celle du modèle courant, et la compatibilité traitée comme une pile de petites traductions posées devant elle. Chaque changement cassant devient une couche. Le système fonctionne, et son prix tient à ce que les couches ne se retirent presque jamais.
Un modèle courant, des couches datées
Chaque intégrateur est épinglé à une version, identifiée par une date, celle du jour où il a créé sa clé, sauf s'il en demande explicitement une autre par un header. Votre code métier et vos contrôleurs ne connaissent que la version la plus récente, et une chaîne de transformations fait le lien entre les deux.
requête du client (version 2024-03-01)
│ monte : chaque couche plus récente adapte la requête
▼
modèle courant ── contrôleur ── réponse courante
│ descend : chaque couche plus récente adapte la réponse
▼
réponse au client (forme 2024-03-01)Un changement cassant s'écrit comme un objet qui sait faire les deux trajets :
interface VersionChange
{
/** Date à partir de laquelle le nouveau comportement s'applique. */
public function introducedOn(): string;
/** Adapte une requête écrite pour la forme précédente. */
public function upgradeRequest(string $route, array $payload): array;
/** Ramène une réponse courante à la forme précédente. */
public function downgradeResponse(string $route, array $payload): array;
}
final class SplitCustomerName implements VersionChange
{
public function introducedOn(): string
{
return '2025-06-01';
}
public function upgradeRequest(string $route, array $payload): array
{
if ($route === 'customers.create' && isset($payload['name'])) {
[$first, $last] = array_pad(explode(' ', $payload['name'], 2), 2, '');
$payload['first_name'] = $first;
$payload['last_name'] = $last;
unset($payload['name']);
}
return $payload;
}
public function downgradeResponse(string $route, array $payload): array
{
if (isset($payload['first_name'], $payload['last_name'])) {
$payload['name'] = trim($payload['first_name'].' '.$payload['last_name']);
unset($payload['first_name'], $payload['last_name']);
}
return $payload;
}
}Pour un client épinglé au 1er mars 2024, on applique toutes les couches dont la date est postérieure, dans l'ordre chronologique à la montée et dans l'ordre inverse à la descente. Un client à jour n'en traverse aucune.
L'exemple mérite qu'on s'y arrête, car le découpage du nom sur la première espace est faux pour une partie des noms. Une couche de compatibilité fait au mieux avec l'information que l'ancienne forme contenait, et ce « au mieux » doit être écrit dans la documentation de la version.
À lire ensuite
Toute la rubrique ArchitectureWeb
Vieilles versions d'app : l'API que vous ne pouvez plus changer
Un binaire mobile publié vit des années et vous lie à chaque réponse d'API qu'il sait lire. Comment fixer une fenêtre de compatibilité, ce qui doit être dans la première version pour pouvoir forcer une mise à jour, et ce que ce pouvoir coûte.
8 minMembres
Architecture
Concevoir une API pour des agents
Ce qui change quand le consommateur de votre API est un modèle qui la relit à chaque appel : les descriptions deviennent le contrat, les erreurs des instructions, et la taille des réponses un budget.
8 minMembres
Architecture
Webhooks fiables : envoyer et recevoir
Ce qu'un webhook garantit vraiment, comment l'émettre sans qu'un destinataire lent bloque les autres, et comment le recevoir sans dépendre de l'ordre ni de l'unicité des livraisons.
7 minMembres