Évolution des données en microservices : éviter les changements cassants grâce à la compatibilité
- 1 décembre 2025
- 6 mins de lecture
- Concepts de programmation , Qualité logicielle
Table des matières
Passer d’un monolithe à une architecture microservices offre une réelle agilité. Mais ce changement ouvre aussi la porte à un défi majeur : la compatibilité des données.
Quand plusieurs services communiquent, une question critique se pose : comment échanger des données sans provoquer de breaking changes à chaque mise à jour ?
Cette problématique dépasse la simple performance. Elle repose d’abord sur trois piliers essentiels :
- Le Protocole de communication (par ex. HTTP pour les requêtes, AMQP pour la messagerie)
- Le Mécanisme de sérialisation des données (la manière d’encoder et décoder les données)
- Les Frameworks/bibliothèques utilisés (comme gRPC, Kafka ou tout simplement REST)
​
Le rôle crucial de la sérialisation
Entre JSON, Protobuf, Avro et d’autres formats, chaque choix implique un compromis sur :
- La vitesse d’encodage/décodage
- La taille des données transmises
- La lisibilité humaine
- La disponibilité cross-langage
- L’évolutivité du format
Les quatre premiers relèvent de la performance et de la logistique.
Mais l’évolutivité conditionne la résilience : si vos données ne peuvent pas évoluer sans casser d’anciens services, votre architecture devient fragile.
​
pourquoi les déploiements cassent les systèmes
L’évolutivité est la capacité à modifier la structure des données échangées sans provoquer de défaillance.
On pourrait penser qu’il suffit de mettre à jour tous les services simultanément. Dans un monde idéal, ce serait vrai. En pratique, c’est rarement possible :
- Les déploiements sont progressifs. La propagation complète prend du temps.
- Les retours arrière existent. Un service peut revenir à une version précédente si un bug survient.
- Certains clients prennent du retard. Les partenaires externes ou les applications utilisateurs peuvent mettre des jours ou semaines à adopter votre nouveau format.
Cela signifie que vous allez forcément gérer des versions coexistantes : des services avec nouvelle structure, et d’autres avec l’ancienne.
​
Les deux règles d’or : compatibilité ascendante et descendante
Pour garantir une évolution continue, vous devez assurer :
-
Compatibilité descendante (backward compatibility) : un service mis à jour doit pouvoir lire des données produites par un service plus ancien.
-
Compatibilité ascendante (forward compatibility) : un service plus ancien doit pouvoir lire (ou ignorer sans erreur) les données produites par un service plus récent Sans ces deux principes de compatibilité, vos déploiements risquent d’introduire des pertes de données ou des défaillances en cascade.
| CONSOMMATEUR (ANCIEN V1) | CONSOMMATEUR (NOUVEAU V2) | |
| PRODUCTEUR (V1) | Fonctionne (cas de base) | Compatibilité descendante (gère les données anciennes) |
| PRODUCTEUR (V2) | Compatibilité ascendante (tolère les données nouvelles) | Fonctionne (cas nouveau) |
​
Stratégie 1 : évolution de schéma compatible
Cette stratégie fonctionne quand les services partagent un schéma commun, un plan formel de la donnée. Des outils comme Protobuf, Avro ou Thrift imposent des règles pour éviter les changements cassants.
Info
Même si JSON est lisible, il est faiblement typé et ne gère pas automatiquement les champs inconnus. Cela rend l’évolution compatible difficile. C’est pourquoi des formats fortement typés comme Protobuf et Avro sont privilégiés en communication interne.
​
Fonctionnement
Vous faites évoluer votre schéma existant, sans changer l’API visible :
-
Champs inconnus : La bibliothèque de sérialisation garantit que les services consomment sans erreur les champs qu’ils ne connaissent pas. Cela assure la compatibilité ascendante et descendante.
-
Ajouter un champ : Le champ doit être optionnel. Les services plus anciens l’ignorent automatiquement.
Les nouveaux services doivent accepter qu’il soit absent dans les anciens messages. -
Supprimer un champ : Les nouveaux services cessent de l’envoyer. Les anciens utilisent la valeur par défaut ou un mécanisme de secours.
Les nouveaux doivent aussi gérer sa disparition dans les anciens messages.
Warning
Modifier un champ existant est extrêmement risqué. Pour préserver la compatibilité descendante, ne changez jamais :
- le type de données (ex.
integer→string), - la sémantique (réutiliser un numéro de champ pour autre chose),
- les dépendances logiques (rendre un champ indispensable alors qu’un service ancien ne l’envoie pas).
Ces changements rendent les déploiements impossibles sans rupture.
​
Exemple (Protobuf)
1// v1
2message User {
3 string username = 1;
4 string email = 2;
5 int32 age = 3;
6}
7
8// v2 (Évolution compatible)
9message User {
10 string username = 1;
11 string email = 2;
12 int32 age = 3 [deprecated = true]; // Les anciens services peuvent encore lire 'age'
13 string birth_date = 4; // Les nouveaux services utilisent 'birth_date'
14}
| Avantages | Inconvénients |
|---|---|
| Un seul schéma à maintenir. | Peu adapté aux gros changements fonctionnels. |
| Compatibilité gérée automatiquement par la bibliothèque. | Accumulation possible de nombreux champs dépréciés. |
​
Stratégie 2 : versionnement d’API
Quand le changement est trop important ou incompatible, vous publiez une nouvelle version d’API. Les versions coexistent jusqu’à la migration complète.
​
Fonctionnement
- Vous gardez l’ancienne API active (ex.
/v1/userdata). - Vous publiez une nouvelle version (ex.
/v2/userdata). - Les clients migrent progressivement.
- Vous supprimez l’ancienne version une fois tout le monde transféré.
Info
Le versionnement peut être fait via le chemin d’URL (/v2/...)
ou via un en-tête (Accept, X-API-Version).
| Version | Endpoint | Changement |
|---|---|---|
| v1 | /v1/userdata |
détails initiaux |
| v2 | /v2/userdata |
modification d’un champ majeur |
| Avantages | Inconvénients |
|---|---|
| Flexibilité totale : tout peut changer. | Plusieurs API à maintenir en parallèle. |
| Les anciens clients sont totalement isolés. | Charge accrue en test, déploiement et maintenance. |
​
Choisir la bonne approche
| Facteur | Évolution de schéma | Versionnement d’API |
|---|---|---|
| Effort de maintenance | Faible | Élevé (multiples API) |
| Flexibilité | Limitée aux changements compatibles | Changements structurels illimités |
| Idéal pour | Microservices internes | API publiques accessibles par des clients externes |
En résumé :
- Si vos changements sont additifs, privilégiez l’évolution de schéma (Protobuf/Avro). C’est le plus simple.
- Si vous avez des clients externes ou un refactoring majeur, utilisez le versionnement d’API.
- Beaucoup d’équipes combinent les deux : — évolution de schéma en interne, — versionnement d’API pour l’externe.
Bulletin d'information
Abonnez-vous à notre bulletin d'information et restez informé(e).
​
Conclusion
L’évolution des structures de données est inévitable dans un système de microservices vivant.
En concevant dès le départ avec la compatibilité ascendante et descendante, vous permettez des déploiements sereins, des mises à jour progressives et une évolution sans chaos.