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 composantuseQuery
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 composantAccountComponent
) - 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 à laAccount
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 fichierCreateTransactionForm
)
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.