Quand les données viennent de loin elles peuvent ne pas être disponibles au moins temporairement, pour les Apps mobiles c’est un problème essentiel à régler… Voyons comment…
Données dans les nuages
Je ne veux pas utiliser ici le mot “nuage” au singulier car il ferait trop penser à une traduction de “cloud” or je ne veux pas parler spécifiquement “du Cloud” ici !
Je veux vous parler des données totalement dématérialisées (si on peut parler de données “matérielles” en informatique !) qui se trouvent “ailleurs”. C’est à dire pas en local sur la machine.
Ces données peuvent bien entendu venir du Cloud, mais en entreprise elles viennent plus souvent de serveurs appartenant à cette dernière. Gestion de clients, d’agenda de techniciens, de données comptables, de stock, de vente… elles sont diverses et dépendent à chaque fois du contexte applicatif et de l’entreprise qui se cache derrière.
Les unités mobiles consomment beaucoup de données qui viennent “d’ailleurs” car dès le départ leurs petites capacités de stockage l’obligeait. Mais aujourd’hui où on stocke aisément sur des machines pas chères 64, 128 voire 256 Go, ce qui est énorme, ce n’est plus le problème de place qui justifie cette délocalisation des données mais bien celui de leur centralisation au contraire. Les unités mobiles sont aussi et avant tout des unités communicantes, elles sont les enfants des premiers téléphones mobiles après tout. L’itinérance est dans leurs gènes. Dans le même temps, et après le mouvement égoïste du Personal Computer stockant “ses” données sur “ses” supports locaux, l’informatique centralisée des débuts à vite repris ses droits. D’abord les réseaux locaux, puis les serveurs d’entreprises puis le Cloud. Permettant de nouveaux d’avoir une centralisation parfaite de toutes les data, l’utilisateur n’ayant plus qu’un terminal pour y accéder. Du 5250 IBM de 1977 (photo ci-contre) des System/34, aux smartphones dernières génération, l’histoire des données en informatique n’est qu’une sorte de boomerang revenant choir au pied de celui qui l’a lancé…Un retour vers le passé, avec des data centralisées et des terminaux, juste plus jolis et permettant aussi de téléphoner.
De fait les unités mobiles consomment de la data qui n’est pas locale. Et cette consommation est liée intiment à leur capacité de communiquer. 2, 3, 4 et bientôt 5G, Wifi, Bluetooth, c’est un concentré de tout ce qui a été inventé en matière de transmission de données qui se trouve là enfermé dans 100 à 200 grammes…
Mais toutes ces capacités dépendent du bon fonctionnement d’une chaîne complexe et longue d’autres machines, d’autres procédés techniques qui assurent le transport des data vers ou depuis l’unité mobile. Et même dans les meilleurs conditions ces moyens connaissent parfois des défaillances, même temporaires, quand ce n’est pas l’utilisateur lui-même qui passant sous un tunnel, entrant dans un ascenseur, descendant dans un sous-sol ne brise ce lien immatériel qui le relie aux données devenues le sang qui irrigue et donne vie à ces machines…
Bref, les données centralisées c’est très pratique, c’est indispensable même, mais quels que soient les progrès leur transmission n’est jamais garantie à 100% !
Et que se passe-t-il quand ce lien électromagnétique vient à faillir, disparaître, s’estomper ?
C’est le crash…
En tout cas si n’est prévu côté applicatif.
Et tout cela nous amène au sujet du jour (quelle coïncidence !) : comment protéger ses applications d’une rupture temporaire des communications sans tout planter et en permettant à l’utilisateur de continuer son travail ou, au minimum, de ne rien perdre de ce qu’il avait entrepris ?
Le data caching
L’idée est vieille comme les données, cacher les données. Non pas les planquer sous le tapis, mais un peu quand même… Les mettre au frais, dans un coin au cas où … C’est le principe.
Un simple lecteur de vidéo en streaming utilise un cache pour amortir les variations de vitesse de transmission pour que l’utilisateur ne connaisse pas de rupture en plein milieu de sa série préférée. Les ordinateurs connectés à des capteurs retournant de grandes quantités de données, les super calculateurs connectés à des radars, des radiotélescopes et cætera eux aussi utilisent des caches parfois énormes pour avoir le temps de digérer les données. Ces caches peuvent même être eux-mêmes des ordinateurs avec stockage local servant d’intermédiaires aux machines encore plus grosses qui ne peuvent s’arrêter de fonctionner en raison de leur gigantesque coût d’exploitation. Les caches peuvent ainsi être utilisés en entrée comme en sortie.
Mais sans aller si loin, une simple transaction bancaire sur smartphone, l’accès à l’agenda des rendez-vous d’un technicien, aussi humbles soient ces données comparées à celles de radars protégeant une nation ou celles émanant des expériences du CERN n’en sont pas moins soumises aux mêmes aléas des transmissions et aux même besoin d’être protégées par du cache ! Les Apps nécessitent le plus souvent un cache en entrée. Il est rarissime que le flux de données généré par la machine soit tel qu’il faille aussi un cache en sortie. En revanche ce besoin spécifique fait partie du fonctionnement de tous les OS pour unités mobiles : les Apps pouvant être switchées, abandonnées temporairement, killées par l’OS il est indispensable de cacher les saisies de l’utilisateur au fur et à mesure afin qu’il ne perde rien entre ces interruptions volontaires ou non. Mais ces caches là sont bien particuliers et je n’en parlerai pas aujourd’hui.
Ce qui va nous intéresser ici ce sont les caches en entrée. Ceux qui vont permettre de pallier aux problèmes évoqués plus haut (interruption momentanées des communications notamment).
Car si les OS mobiles prévoient tous des procédés de cache pour les saisies en cours, il n’y a aucune norme ni obligation concernant les données venant de l’extérieur, c’est au développeur d’y penser et de mettre en place des solutions ad hoc.
Lorsqu’une communication essentielle est brisée le logiciel peut-il maintenir son fonctionnement ?
C’est une question essentielle à se poser en premier lieu. Car si la réponse est non, à quoi bon vouloir complexifier les choses…
La réponse dépend de l’App bien évidemment, mais pour un très grand nombre la réponse sera oui. Pas un oui franc et massif, un “oui mais”, ce qu’on appelle plus élégamment un “mode de fonctionnement dégradé”. C’est à dire un mode dans lequel l’utilisateur perd temporairement certaines fonctions ou facilités mais dans lequel il peut malgré tout continuer à utiliser certaines fonctions de l’App en attendant le retour de la Sainte Connexion.
Gérer le mode dégradé
C’est bien ce mode, dit dégradé, que nous allons tenter de mettre œuvre ici. Les Apps ne pouvant supporter la moindre rupture de communication s’arrêteront sagement et poliment avec une message d’erreur, sauront reprendre plus tard leur travail. Celles pouvant supporter une interruption de communication temporaire vont devoir implémenter un mode dégradé. Comme ce cas est fréquent il est intéressant d’y trouver des réponses simples et facilement reproductibles.
C’est pour cela qu’ici je vais vous présenter une solution de ce type faisant appel à une librairie écrite par J.Montemagno bien connu dans le monde Xamarin.Forms : Monkey Cache.
L’histoire raconte que James a laissé un tweet un jour pour demander quel était la bonne librairie à utiliser pour ce problème de data caching et ne voyant venir aucune réponse ou des propositions peu satisfaisantes il s’est alors lancé dans l’écriture de Monkey Cache.
Il voulait quelque chose de simple et d’efficace, utilisable un peu partout et ainsi est né cette librairie en Open Source sur GitHub à laquelle vous pouvez même participer pour l’améliorer si vous le désirez.
Mise en œuvre
Pour mettre en évidence comment se servir de cette librairie il faut une App qui consomme des données distantes. Ce qui implique pour votre serviteur d’avoir préalablement conçu une application ASP Core sachant servir des données. Je passerai les détails de cette application la création de services Web sous ASP.NET Core supportant les opérations CRUD sur des données n’étant pas le sujet du jour. Mais il est bon d’avoir conscience que derrière un article aussi humble que le présent se cache parfois des heures de travail absolument invisibles !
Je passerai aussi sous silence le formalisme JSON utilisé pour transmettre les données dans les deux sens, l”utilisation de traducteurs JSON vers C# pour créer les classes côté client, et tout ce qui fait cette merveilleuse plomberie.
Sachez uniquement que derrière l’App de démo d’aujourd’hui se … cache, et par force, un serveur de données, un service Web REST qui sera connecté pour montrer l’échange de données et déconnecté pour montrer le mode dégradé.
Une barrique !
La librairie Monkey Cache offre un conteneur dont la mémorisation peut être choisie entre base de données ou fichier disque. Ce conteneur c’est une barrique ! En effet Il s’appelle Barrel.
Il y a plusieurs paquets Nuget : le principal qui contient la logique de la barrique (le conteneur) et plusieurs paquets pour le stockage (SQLite, LiteDB et FileStore). On prend systématiquement le premier paquet et on choisit parmi les autres celui qui sied le mieux à l’App et son contexte, à savoir donc, un stockage en base de données (SQLite ou LiteDB) ou en fichier (FileStore).
Une fois le provider de stockage choisi et initialisé, la barrique nous offre une API très simple :
- Un setup minimaliste dans lequel il faut juste donner un nom pour notre stockage en le passant à la propriété ApplicationId.
- Une opération de sauvegarde par Add(key, data, expireIn), qui permet de stocker des data possédant une clé pour les recharger et un indicateur d’expiration donnant une durée de vie au cache pour la clé spécifiée (on peut ainsi gérer des durées personnalisées pour chaque data stockée)
- Une opération de chargement des données par Get<DataType>(key) permettant de recharger les données correspondant à une clé spécifique le tout de façon typée
- Une opération de suppression EmptyAll() dont le rôle est de vider la totalité du cache.
Comme on le voit, la barrique possède une fonctionnement très simple qui se résume vraiment au strict nécessaire. Et c’est bien !
Exemple
Comme indiqué plus haut pour créer un exemple consommant de la donnée il faut un serveur. Ce serveur a été créé à part en utilisant ASP.NET Core, il gère une liste d’étudiants avec un nom, un âge et un numéro de code. Quelque chose de très minimaliste mais qui réalise toutes les opérations CRUD sur la liste via un service Web. Ce serveur créée par défaut une liste d’étudiants car il n’y a pas de persistance des données (ce n’est qu’une démo).
Une fois cela posé il nous faut maintenant créer l’App qui nous intéresse plus directement : celle qui va consomme les données “distantes” et qui va utiliser Monkey Cache pour se préserver des interruptions temporaires de réseau.
Comme déjà expliqué il existe plusieurs paquets Nuget :
Le premier est obligatoire, il fixe le mécanisme du cache. Et pour cette exemple nous allons nous servir du provider de stockage en fichier donc le second sur la liste ci-dessus (FileStore).
Initialisation
La première véritable étape de programmation consiste alors à initialiser la barrique… Autant le faire le plus tôt possible , c’est-à-dire dans le constructeur de App.Xaml.cs et juste avant l’appel à la MainPage. :
Consommer les données
Ce n’est pas la partie que je veux montrer dans cet article, mais il va bien falloir se connecter au serveur, réclamer des données et les afficher. Tout cela sera fait dans la page unique de l’App de démo (MainPage). Le service web sera utilisé pour retourner une liste d’étudiants qui sera affichée dans une ListView. Je vous laisse imaginer ce montage très simple. Parallèlement il faudra aussi récupérer la classe des données, soit par un partage de code entre le serveur et l’App, soit par rétro-analyse du JSON qui sera transmis afin de créer une classe utilisable côté App. C’est l’option choisie ici en utilisant le site https://jsonutils.com/ qui offre un traducteur JSON/C#. Par le biais d’une application de test spécialisée SoapUI installée sur ma machine j’ai pu tester mon service web et faire des requêtes, notamment celle qui retourne la liste des étudiants. De là j’ai copié la réponse JSON que j’ai collé dans le site web JsonUtils indiqué ci-avant. Il a alors fourni, selon des réglages à choisir, le code C# suivant :
Une fois adapté (renommage de la classe principalement) ce code est ajouté au projet de l’App, idéalement dans un sous-répertoire Model. L’App de démo étant très simple je n’ai pas ajouté tous les répertoires habituels (View, ViewModel…) tout se trouvera donc dans la racine.
Pour demander les données au serveur il existe plusieurs façons de faire, ici non plus je n’entrerai pas dans les détails car ce n’est pas le sujet. On va donc supposer une méthode asynchrone qui sait faire la bonne requête au serveur elle va ressembler à cela :
Pour l’instant pas de barrique à l’horizon… On va l’ajouter.
Première étape : mettre les données dans le cache quand on les obtient. Pour ce faire juste avant le “return list” on va ajouter le stockage de la réponse par :
La clé utilisée est l’URL, c’est une bonne stratégie de faire de la sorte, chaque requête au serveur étant repérée par sa propre URL ce qui évite d’inventer des noms de clé sans rapport. Les data sont directement stockées tel quel, la barrique se charge de tout ! Quant au “CacheDuration” c’est une constante définie par l’App, c’est un TimeSpan, il indique la durée durant laquelle les données restent cachées. Passé ce délai le cache ne retourne rien, il n’y a plus de données. Selon l’App et son contexte chacun fixera la durée qu’il veut, une journée entière pourquoi pas.
Reste maintenant à détecter une défaillance réseau et à obtenir les données du cache, cela va se trouver au tout début de la méthode montrée plus haut.
En utilisant les facilités de Xamarin.Essentials on peut tester si le réseau est disponible ou pas. On en profite pour vérifier si le cache dont on a besoin n’a pas expiré.
Si toutes les conditions sont réunies (réseau non accessible, cache disponible) on va chercher les données dans la barrique et on les retourne.
Bien entendu comme l’indique le commentaire il faudra prévoir d’envoyer un message à l’utilisateur pour le prévenir qu’il voir des données en cache car la connexion Internet n’est pas disponible.
De même que si le cache a expiré il faut pouvoir le prévenir qu’il ne voit rien car il n’y a ni réseau ni données en cache.
Tout cela est hors sujet et dépendra aussi des librairies que vous utilisez pour afficher des popup ou des toasts.
Conclusion
Gérer l’absence temporaire de connectivité réseau est essentiel dès lors que l’App fonctionne avec des données distantes. Le plus simple est d’utiliser un cache pour masquer des pannes temporaires sans priver l’utilisateur des données assez récentes qu’il avait pu obtenir. Il n’y a donc pas interruption totale de l’App, juste un mode de fonctionnement un peu dégradé. Mais souvent cela suffira pour consulter un agenda, une liste de produits qui ne change pas tous les jours etc.
De même l’utilisation du cache comme dans l’exemple proposé permet de limiter drastiquement les flux réseau vers le serveur ! On peut utiliser un cache de quelques minutes ou de 30 secondes ou moins s’il s’agit d’opération très fréquentes. Ce sont des tas d’appels au serveur qui seront alors servis en local par le cache. Vitesse côté App, économie de ressources côté serveur… C’est un sacré gain ! Il ne s’agit plus ici de pallier aux problèmes de connectivité mais d’utiliser le cache comme stratégie d’accélération des accès réseaux. Les deux approches peuvent être mélangées pour offrir un confort encore plus grand à l’utilisateur.
Il n’y a ainsi des tas de bonnes raisons d’utiliser Monkey Cache !
Stay Tuned !