La Résilience améliore l’expérience utilisateur (1/2)

Aurelien Vanhoucke
6 min readDec 18, 2019

--

Illustration : @jcarreaud

En quoi la “resilience by design” peut améliorer l’expérience utilisateur, quels mécanismes sont à disposition et peuvent être mis en place, peut-on en combiner plusieurs ?

La résilience, un joli mot vous ne trouvez pas? En informatique, la résilience est la capacité d’un système à continuer de fonctionner en cas de panne. (mais pas que…)

Lorsque nous développons une application, notre principal objectif est que l’utilisateur puisse avoir accès à l’information quand il le souhaite…

Malheureusement, il arrive que celle-ci ne soit pas disponible…

Pour cela, on va pouvoir utiliser plusieurs mécanismes dit “pattern”, permettant d’améliorer l’expérience utilisateur, tout en minimisant les affichages horribles qui peuvent frustrer l’utilisateur, qui va rapidement perdre patience…

“je ne parle pas anglais moi !” 😠 “ça ne marche jamais ce site” 😞

Nous allons voir ici quelques patterns de résilience disponibles, et nous essaierons dans d’autres articles, de vous en faire découvrir davantage…

1/ La réponse partielle :

Illustration : @jcarreaud

Le principe de la réponse partielle consiste à préférer donner une partie de l’information plutôt que rien du tout…

Exemple : Je dois afficher les membres du foyer d’un client pour qu’il choisisse ceux impactés par la mise à jour du contrat.

Lorsque je récupère les membres du foyer, j’appelle 2 services :

A- le service “enfants”, qui ne répond pas

B- le service “conjoint” qui lui répond

Nous avons donc 2 solutions :

  1. J’affiche le conjoint, et j’indique dans un message d’information à mon utilisateur que : “Un problème est survenu lors de la récupération des données « Enfant »”
  2. Je n’affiche rien et indique dans un message d’erreur que : “Un problème est survenu lors de la récupération des membres du foyer”

Le choix reviendra à l’équipe produit. Dans notre exemple ci-dessus, si dans 90% des cas, le conjoint est le seul membre ajouté au contrat, on privilégiera la solution 1, dite Réponse partielle.

Il y a plusieurs solutions et manières de faire de la réponse partielle, chaque situation doit être étudiée en fonction du contexte pour satisfaire au mieux l’utilisateur. Il faut donc peser le pour et le contre dans chaque cas fonctionnel : il ne faut pas vouloir à tout prix, afficher ce que l’on a réussi à récupérer, car cela peut également rendre l’information incorrecte.

Ce pattern de résilience peut être associé à d’autres patterns : le circuit breaker, le timeout, le retry, le cache

2/ Circuit Breaker (ou mode dégradé)

Illustration : @jcarreaud

Le Circuit Breaker est un mécanisme permettant de changer rapidement et automatiquement de service en cas d’avarie du service principal.

Si un service ne fonctionne pas correctement (un nombre d’erreurs techniques significatif sur une période donnée), on peut rediriger sur le service secondaire, et ainsi renvoyer des données à l’utilisateur (peut-être moins à jour, moins complète, mais au moins on renvoie des données). Après une période définie et confirmation de la remise en l’état du service principal, la (re)bascule sur ce service est automatique.

Dans notre exemple, on pourrait, si on se rend compte que le service “enfants” ne répond pas correctement depuis 1h, passer sur le service “enfants_backup” qui est une copie/cache (voir §4) des données enfants.

Remarque : dans ce cas, il faut indiquer à l’utilisateur que cette donnée n’est pas forcément la plus à jour…

3/ Timeout & Retry : le duo gagnant

Illustration : @jcarreaud

Le timeout correspond au temps de réponse maximum d’un appel de service. Ce délai dépassé, la réponse est renvoyée avec une erreur (HTTP 408). Par défaut, ce délai est long : 30, voire 60 secondes. La règle pour définir le bon timeout est d’observer le temps de réponse moyen de ce service, et d’y ajouter une marge.

Le retry correspond au fait de recommencer plusieurs fois l’appel d’un service : en général, 3 appels successifs séparés par une pause d’1 seconde augmente les chances d’avoir un résultat.

Attention, cette association de patterns n’est pas envisageable sur tous les appels de service, en l’occurrence un appel de service qui viendrait modifier une donnée… au risque de modifier finalement plusieurs fois la donnée, et donc la rendre incohérente (mise à jour de la remise d’un article dans le panier : 10€ auquel on applique 1€ de réduction… on risque avec le pattern retry d’appliquer plusieurs fois cette réduction… 😿).

Si l’on reprend notre exemple, tant que le service “enfant” ne répond pas, l’utilisateur obtient au mieux un “Chargement en cours” / loader qui tourne pendant 60 secondes. Afin d’optimiser l’expérience utilisateur, changeons de méthode en diminuant le timeout (de 60 à 1 seconde : le temps de réponse moyen constaté dans 80% des cas est <400ms pour ce service), et en appelant successivement ce service à 3 reprises avec une pause d’1 seconde entre chaque appel si celui-ci ne répond pas dans les temps…

Résultat : on aura 3 fois plus de chance d’obtenir le résultat escompté, et l’utilisateur attendra maximum 6 secondes (20 fois moins longtemps) le chargement de la page… 🙌 😺

Ces 2 patterns sont complémentaires : Il est préférable de diminuer le temps de réponse d’un appel de service (timeout) et d’essayer plusieurs fois d’obtenir la réponse (retry).

4/ Le cache :

Illustration : @jcarreaud

Un cache logiciel permet à une application d’éviter de répéter des appels de méthodes coûteux en stockant le résultat d’un appel en mémoire, pour une durée donnée.

Pour la consultation de données qui ne changent pas souvent, il convient d’en mettre le maximum en cache (quand c’est possible). Un cache http bien utilisé c’est de la charge server/réseau en moins et des réponses plus rapide pour l’utilisateur.

Dans notre exemple précédent, on a toujours le service “conjoint” qui répond, mais le service “enfant” ne répond toujours pas. Par contre, le client s’est connecté hier, et nous avions stocké en mémoire le résultat du service “enfant”.

Nous pouvons donc lui afficher ses enfants, en lui indiquant via un message informatif que si un enfant a été ajouté depuis, il ne figurera pas forcément dans les résultats, car nous utilisons une donnée en mémoire et non en live…

Conclusion : grâce à ces 5 patterns de résilience, nous avons réussi à afficher à l’utilisateur les données qu’il souhaitait, en passant de 60 secondes sans certitude de réponse à 8 secondes avec une réponse (en cache ou non, via un service différent) et un message explicatif clair : Resilience Power ! 😸

Nous verrons dans un prochain article, d’autres patterns de résilience comme le Blue Green Deployment, le Canary Deployment ou encore le Feature Flipping

Exemple : on peut montrer en quelques lignes de code, une implémentation de 3 des 5 patterns présentés précédemment

On réalise ici 3 appels successifs (avec un intervalle de 2 secondes entre chaque appel) avec 1 seconde de timeout, la réponse est mise en cache 4h

--

--