Quand votre application mobile plante parce que les utilisateurs ne mettent pas à jour

Vous déployez un nouvel endpoint backend. La dernière version de l'application le gère parfaitement. Tout est vert dans votre pipeline CI/CD. Puis les rapports de crash commencent à arriver.

Les utilisateurs sur d'anciennes versions de l'application frappent le même endpoint, mais leur application ne peut pas analyser le nouveau format de réponse. Ils obtiennent un écran blanc, un crash, ou pire — une perte de données silencieuse. Vous n'avez pas changé le backend pour tout le monde. Vous l'avez changé pour la dernière version de l'application. Mais le backend ne fait pas de discrimination. Il sert la même réponse à chaque client qui demande.

C'est le coût caché de la livraison mobile. Contrairement aux applications web où vous contrôlez le client, les applications mobiles vivent sur des appareils que vous ne possédez pas. Les utilisateurs décident quand mettre à jour. Certains mettent à jour immédiatement. Certains attendent des semaines. Certains utilisent encore une version que vous avez livrée il y a six mois. Et votre backend continue d'évoluer entre-temps.

Le problème du décalage de version

La tension centrale est simple : le backend change continuellement, mais les clients mobiles se mettent à jour selon leur propre calendrier. Chaque fois que vous ajoutez un nouvel endpoint, modifiez une structure de réponse ou dépréciez un champ, vous créez un point de rupture potentiel pour les anciennes versions de l'application.

Le diagramme de séquence suivant montre comment un changement backend peut casser les anciennes versions de l'application tandis que les nouvelles versions fonctionnent correctement :

sequenceDiagram participant AppV1 as App v1.0 participant AppV2 as App v2.0 participant Backend as Backend Note over Backend: Le backend évolue AppV1->>Backend: GET /api/orders (ancien format) Backend-->>AppV1: 200 OK (ancienne réponse) Note over AppV1: Fonctionne correctement AppV2->>Backend: GET /api/v2/orders (nouveau format) Backend-->>AppV2: 200 OK (nouvelle réponse) Note over AppV2: Fonctionne correctement AppV1->>Backend: GET /api/v2/orders (nouveau format) Backend-->>AppV1: 200 OK (nouvelle réponse) Note over AppV1: CRASH - ne peut pas analyser le nouveau format Note over Backend: Solution : garder l'ancien endpoint pour la rétrocompatibilité

La plupart des équipes ne réalisent pas cela jusqu'à ce que la production casse. Ils testent la dernière application contre le dernier backend, tout passe, et ils livrent. Mais la suite de tests n'a jamais exécuté l'ancienne application contre le nouveau backend. Cette combinaison est invisible jusqu'à ce que de vrais utilisateurs la rencontrent.

Le problème s'aggrave à mesure que votre base d'utilisateurs grandit. Plus d'utilisateurs signifie plus de versions en circulation. Chaque version a ses propres attentes sur ce que le backend devrait renvoyer. Votre backend doit satisfaire toutes ces attentes simultanément, au moins pendant un certain temps.

Sachez quelles versions sont en circulation

Avant de pouvoir gérer la compatibilité, vous avez besoin de visibilité. Les magasins d'applications vous donnent quelques données — Google Play Console et App Store Connect montrent tous deux la distribution des versions. Mais ces données sont retardées et agrégées. Elles vous disent ce que les utilisateurs ont installé, pas ce qu'ils utilisent activement.

Une meilleure approche : envoyez la version de l'application avec chaque requête. Ajoutez un en-tête personnalisé comme X-App-Version ou encodez-la dans votre chaîne User-Agent. Votre backend enregistre cette information, et vous pouvez l'agréger dans un tableau de bord montrant l'adoption des versions en temps réel.

Ces données répondent à des questions critiques :

  • Quel pourcentage d'utilisateurs actifs est sur chaque version ?
  • À quelle vitesse les utilisateurs adoptent-ils la dernière version ?
  • Quelles anciennes versions ont encore un trafic significatif ?
  • Quand pouvez-vous abandonner en toute sécurité le support d'une version héritée ?

Sans ces données, vous prenez des décisions à l'aveugle. Vous pourriez déprécier une version qui a encore 30 % d'utilisateurs actifs, ou continuer à supporter une version que seulement 2 % utilisent.

Gardez le backend compatible

L'approche standard est la rétrocompatibilité. Lorsque vous modifiez un endpoint, ne supprimez pas immédiatement l'ancien format de réponse. Ajoutez les nouveaux champs à côté des anciens, ou versionnez explicitement vos endpoints API.

Par exemple, au lieu de modifier /api/orders, créez /api/v2/orders et gardez /api/v1/orders en fonctionnement. Votre dernière application parle à v2, les anciennes applications continuent d'utiliser v1. Cela donne aux utilisateurs le temps de mettre à jour sans casser leur expérience.

Mais la rétrocompatibilité a des limites. Vous ne pouvez pas maintenir cinq versions de chaque endpoint indéfiniment. Le coût augmente avec chaque version que vous supportez. À un moment donné, vous devez couper les anciennes versions.

C'est là que votre surveillance des versions devient essentielle. Lorsque les données montrent qu'une version héritée est tombée en dessous d'un seuil acceptable — disons 5 % des utilisateurs actifs — vous pouvez annoncer la dépréciation. Envoyez une notification dans l'application demandant aux utilisateurs de mettre à jour. Donnez-leur une date limite. Après cette date, l'ancien endpoint cesse de fonctionner.

Forcez les mises à jour lorsque c'est nécessaire

Parfois, vous ne pouvez pas attendre une adoption progressive. Les correctifs de sécurité, les corrections de bugs critiques ou les changements réglementaires peuvent nécessiter des mises à jour immédiates. Dans ces cas, vous avez besoin d'un mécanisme pour forcer les utilisateurs à mettre à jour.

Le modèle est simple : votre backend vérifie l'en-tête X-App-Version sur chaque requête. Si la version est inférieure à un seuil minimum, le backend renvoie un code de réponse ou une charge utile spéciale. L'application détecte cela et affiche un écran de mise à jour obligatoire. L'utilisateur ne peut pas continuer tant qu'il n'a pas téléchargé la dernière version depuis le magasin.

C'est un instrument brutal. Utilisez-le avec parcimonie. Chaque mise à jour forcée crée des frictions et risque des avis négatifs. Mais quand vous en avez besoin, c'est mieux que de laisser les utilisateurs sur une version vulnérable ou cassée.

Utilisez la configuration à distance comme filet de sécurité

La configuration à distance vous donne un terrain d'entente entre la compatibilité totale et les mises à jour forcées. Au lieu de changer le code, vous changez la configuration depuis le serveur. L'application récupère cette configuration périodiquement — URLs, timeouts, feature toggles, versions d'endpoint — sans nécessiter de mise à jour du magasin.

Voici comment cela aide avec les problèmes de compatibilité. Supposons que votre dernière version d'application ait un bug qui n'apparaît qu'avec un endpoint backend spécifique. Vous ne pouvez pas corriger l'application rapidement car une révision du magasin prend des jours. Mais vous pouvez modifier la configuration à distance pour pointer cette version d'application vers un endpoint plus ancien et stable. Le bug disparaît sans une seule ligne de code changée.

La configuration à distance aide également lors des déploiements progressifs. Si vous remarquez que les utilisateurs sur la version 4.2 plantent sur une nouvelle fonctionnalité, vous pouvez désactiver cette fonctionnalité pour la version 4.2 uniquement, tout en la gardant active pour la version 4.3. La configuration est sensible à la version, donc chaque version d'application reçoit le comportement qu'elle peut gérer.

Voici à quoi pourrait ressembler une charge utile de configuration à distance sensible à la version :

{
  "config": {
    "new_checkout_flow": {
      "enabled": true,
      "disabled_versions": ["4.2.0", "4.2.1"]
    },
    "api_base_url": "https://api.example.com/v2",
    "legacy_api_base_url": "https://api.example.com/v1",
    "timeout_ms": 10000
  },
  "flags": {
    "dark_mode": true,
    "experimental_search": false
  }
}

L'application lit la liste disabled_versions et ignore le nouveau flux de paiement pour les versions 4.2.0 et 4.2.1, revenant à l'ancien flux. Aucune mise à jour de l'application nécessaire.

Feature flags pour la récupération d'urgence

Les feature flags fonctionnent de manière similaire mais se concentrent sur l'activation/désactivation de fonctionnalités plutôt que sur les valeurs de configuration. Lorsqu'une nouvelle fonctionnalité cause des problèmes en production, vous désactivez le flag depuis votre tableau de bord. La fonctionnalité disparaît de l'application. Les utilisateurs ne la voient pas, ne plantent pas dessus et ne s'en plaignent pas.

L'avantage par rapport à la configuration à distance est la granularité. Vous pouvez cibler des segments d'utilisateurs spécifiques, des régions ou des versions d'application. Vous pouvez augmenter progressivement. Vous pouvez faire des tests A/B. Et quand quelque chose tourne mal, vous pouvez tuer la fonctionnalité instantanément sans nouvelle version.

Les feature flags sont particulièrement précieux pour le mobile car ils découplent le déploiement de la publication. Vous pouvez livrer du code caché derrière un flag, l'activer quand vous êtes prêt, et le désactiver si les choses tournent mal. L'application n'a pas besoin de changer. Le serveur de flags gère tout.

Liste de contrôle pratique pour la gestion des versions

  • Envoyez la version de l'application avec chaque requête via un en-tête personnalisé
  • Construisez un tableau de bord montrant la distribution des versions en temps réel
  • Gardez les anciens endpoints API en fonctionnement jusqu'à ce que les versions héritées tombent en dessous de 5 % d'utilisation
  • Définissez une version minimale prise en charge et appliquez-la avec un mécanisme de mise à jour forcée
  • Implémentez une configuration à distance pour les changements de comportement côté serveur sans mises à jour de l'application
  • Utilisez des feature flags pour désactiver instantanément les fonctionnalités problématiques
  • Annoncez les délais de dépréciation via des notifications dans l'application avant de couper le support

L'essentiel à retenir

La livraison mobile ne se termine pas lorsque le build est signé et téléchargé sur le magasin. Elle se termine lorsque chaque utilisateur est sur une version qui fonctionne avec votre backend actuel. Cela peut prendre des semaines ou des mois. Pendant ce temps, votre backend changera, et chaque changement est une rupture potentielle pour quelqu'un qui utilise encore une ancienne version.

Surveillez votre distribution de versions. Gardez la rétrocompatibilité aussi longtemps que c'est pratique. Utilisez la configuration à distance et les feature flags pour gagner du temps quand les choses tournent mal. Et quand vous devez forcer une mise à jour, faites-le délibérément, pas réactivement.

L'objectif n'est pas d'éliminer la fragmentation des versions — c'est impossible. L'objectif est de savoir ce qui est en circulation, de le garder fonctionnel et d'avoir un plan pour quand ce ne l'est pas.