17 votes

Existe-t-il un moyen de détecter quand les données utilisées par une requête sont évincées du cache du client Apollo ?

Je vous prie de m'excuser pour cette longue question, mais j'aimerais avoir des idées ou de l'aide sur la meilleure stratégie à adopter pour l'invalidation du cache et la récupération des requêtes dans Apollo Client 3.

Contexte

Tout d'abord, quelques informations sur le scénario que j'imagine :

  • Il existe un Account (exemple ci-dessous) qui utilise le composant useQuery hook de react-apollo pour récupérer et afficher quelques informations de base sur un compte et une liste de transactions pour ce compte
  • Ailleurs dans l'application, il y a une CreateTransactionForm qui utilise une mutation pour insérer une nouvelle transaction. Il s'agit d'un composant distinct qui se trouve à un endroit différent de l'arbre des composants et qui n'est pas nécessairement un enfant du composant AccountComponent )
  • Il est important de noter que le processus de stockage d'une transaction sur le serveur a des effets secondaires non négligeables, outre l'insertion de la transaction dans la base de données :
    • toutes les autres transactions qui ont lieu après celui qui est inséré (chronologiquement) est mis à jour avec les nouveaux soldes courants.
    • tout(s) compte(s) associé(s) est (sont) mis à jour avec un nouveau solde courant.

Une version simpliste de mon Account pourrait ressembler à quelque chose comme ceci :

import { gql, useQuery } from '@apollo/client';
import React from 'react';
import { useParams } from 'react-router-dom';

const GET_ACCOUNT_WITH_TRANSACTIONS = gql`
  query getAccountWithTransactions($accountId: ID!) {
    account(accountId: $accountId) {
      _id
      name
      description
      currentBalance
      transactions {
        _id
        date
        amount
        runningBalance
      }
    }
  }
`;

export const Account: React.FunctionComponent = () => {
  const { accountId } = useParams();
  const { loading, error, data } = useQuery(GET_ACCOUNT_WITH_TRANSACTIONS, {
    variables: { accountId },
  });

  if (loading) { return <p>Loading...</p>; }
  if (error) { return <p>Error</p>; }

  return (
    <div>
      <h1>{data.account.name}</h1>

      {data.account.transactions.map(transaction => (
        <TransactionRow key={transaction._id} transaction={transaction} />
      ))}
    </div>
  );
};

Stratégies potentielles

J'évalue les différentes options permettant d'invalider certaines parties du cache du client Apollo et de récupérer les données appropriées après l'insertion d'une transaction. D'après ce que j'ai appris jusqu'à présent, il existe quelques stratégies potentielles :

a) appeler le refetch retournée par useQuery pour forcer le Account pour recharger ses données

  • cela semble fiable et retournerait au serveur pour obtenir des données fraîches, mais la CreateTransactionForm devrait être (directement ou indirectement) couplée à la Account car quelque chose doit déclencher cet appel à refetch

b) passer le nom de la requête ( getAccountWithTransactions ) dans le refetchQueries option de la mutation

  • similaire à un, mais avec un couplage potentiellement encore plus étroit - la CreateTransactionForm devrait connaître tous les autres composants/quêtes qui existent dans l'application et qui pourraient être affectés par la mutation (et si d'autres sont ajoutés à l'avenir, il faudrait se souvenir de mettre à jour le fichier CreateTransactionForm )

c) modifier manuellement le contenu du cache après avoir effectué des mutations

  • J'imagine que cela serait assez complexe/difficile à maintenir parce que le système de gestion de l'information de l'entreprise (SGI) ne peut pas être utilisé. CreateTransactionForm aurait besoin de savoir quelles données exactement a changé suite aux actions du serveur. Comme nous l'avons mentionné, il peut s'agir d'une quantité de données non négligeable et, après avoir effectué la mutation, nous devrons récupérer les données mises à jour non seulement de la transaction qui a été insérée, mais aussi de toutes les autres qui ont été mises à jour par effet secondaire, ainsi que des comptes affectés, etc. Cela pourrait également ne pas être très efficace car certaines de ces informations pourraient ne plus jamais être consultées par le client.

Mon intuition est qu'aucune des options ci-dessus ne me semble idéale. En particulier, je m'inquiète de la maintenabilité de l'application au fur et à mesure de son développement ; si les composants doivent avoir une connaissance explicite des autres composants/requêtes susceptibles d'être affectés par les changements apportés au graphe de données, il me semble qu'il serait très facile d'en manquer un et d'introduire des bugs subtils lorsque l'application deviendra plus grande et plus complexe.

Un meilleur moyen ?

Je suis très intéressé par le nouveau evict et gc introduites dans Apollo Client 3 et je me demande si elles ne pourraient pas fournir une solution plus nette.

Ce que j'envisage, c'est qu'après avoir appelé la mutation, je pourrais utiliser ces nouvelles capacités pour :

  • expulser agressivement le transactions tableau sur tous les comptes qui sont inclus dans la transaction
  • aussi, expulser le currentBalance sur tous les comptes concernés

par exemple :

  const { cache } = useApolloClient();
  ...
  // after calling the mutation:
  cache.evict(`Account:${accountId}`, 'transactions');
  cache.evict(`Account:${accountId}`, 'currentBalance');
  cache.gc();

Ce qui précède fournit un moyen facile de supprimer les données périmées du cache et garantit que les composants iront sur le réseau la prochaine fois que ces champs seront interrogés. Cela fonctionne bien si je navigue vers une autre page et que je reviens à l'application Account par exemple.

Ma question principale (enfin !)

Cela nous amène à la principale pièce du puzzle dont je ne suis pas sûr :

Existe-t-il un moyen de détecter que tout ou partie des données référencées dans une requête ont été évincées du cache ?

Je ne suis pas sûr qu'il soit possible d'attendre cela de la bibliothèque, mais si c'est possible, je pense que cela pourrait permettre de simplifier le code et de réduire le couplage entre les différentes parties de l'application.

Je pense que cela permettrait à chaque composant de devenir plus "réactif" - les composants savent simplement de quelles données ils dépendent et chaque fois que ces données manquent dans le graphe sous-jacent mis en cache, ils pourraient immédiatement réagir en déclenchant une nouvelle recherche sur leur propre requête. Ce serait bien que les composants réagissent de manière déclarative aux changements dans les données dont ils dépendent, plutôt que de communiquer impérativement pour déclencher des actions les uns sur les autres, si cela a un sens.

1voto

Jack Points 471

Il y a deux réponses à cette question : quelle est la pratique standard pour faire cela ? Et comment pouvons-nous faire Une meilleure solution ?

Pratique standard

https://www.apollographql.com/blog/when-to-use-refetch-queries-in-apollo-client/

Apollo sur leur blog et dans leur documentation déclare que si votre mutation a des effets secondaires à l'extérieur de de la requête retournée, alors vous doit utiliser une fonction de mise à jour si vous voulez maintenir la cohérence du cache. Ceci est similaire à l'utilisation de refetchQueries mais elle est plus explicite car vous effectuez vous-même la mise à jour du cache (elle nécessite également moins de requêtes réseau). Cependant, vous avez raison de dire que cette approche entraînera un couplage entre la mise à jour de la mutation de votre transaction et tous les autres composants dépendants.

Je pense que la meilleure façon de la structurer serait alors de définir une fonction de mise à jour pour tous les composants dont la mutation a des effets secondaires. Et ensuite, la transaction appellerait toutes les fonctions de mise à jour des composants qu'elle affecte. Et ceux-ci appelleraient à leur tour leurs propres dépendances. Cela pourrait devenir compliqué, alors voyons si vous pouvez faire le contrôle du cache vous-même.

La meilleure façon de faire

https://www.apollographql.com/docs/apollo-server/data/data-sources/#using-memcachedredis-as-a-cache-storage-backend

Je ne pense pas que vous puissiez vérifier le cache d'Apollo pour savoir si les données sont présentes, mais vous pourriez vérifier un cache Redis pour cette même information et Apollo vous permet de brancher d'autres implémentations de cache. Vous pourriez même écrire votre propre implémentation de cache et utiliser Redis comme backend pour votre implémentation. Cela vous permettrait d'avoir un contrôle plus fin sur le cache que vous souhaitez. Mais cela implique un coût d'ingénierie initial considérable.

Conclusion :

Puisque vos effets secondaires semblent se situer dans les composants transaction et compte (seulement deux composants), je dirais que vous devriez écrire une fonction de mise à jour personnalisée pour le moment. Une fois que vous aurez un problème plus complexe de cohérence du cache, je pense que vous devriez vous plonger dans une implémentation personnalisée de la mise en cache.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X