Déduplication
Le problème
Plusieurs widgets ou services peuvent demander indépendamment les mêmes données au même moment : au chargement de page, lors d'une navigation rapide, ou quand un feature flag est évalué. Sans déduplication, chaque appel déclenche sa propre requête réseau :
Sans déduplication :
Widget A → GET /users/1 ←─┐
Widget B → GET /users/1 ←─┤ 3 appels réseau identiques
Widget C → GET /users/1 ←─┘
Cela gaspille de la bande passante, augmente la charge serveur et risque des conditions de course où les réponses arrivent dans le désordre.
Comment Qora déduplique
Qora suit chaque requête en cours dans une map _pendingRequests indexée par la clé de requête normalisée. Quand un second appel arrive pendant que le premier est encore en attente :
- Le
Futureen cours est réutilisé. Aucun nouvel appel réseau n'est émis. - Tous les appelants attendent le même résultat.
- Le cache est mis à jour une seule fois quand la réponse unique arrive.
- Tous les streams
watchQueryactifs reçoivent la mise à jour d'état.
Avec Qora :
Widget A ─────────────────┐
Widget B → fetchQuery → dédup → GET /users/1 (1 requête)
Widget C ─────────────────┘
les trois se résolvent avec le même résultat
Exemple
// Les trois appels se déclenchent simultanément : une seule requête HTTP est émise
final f1 = client.fetchQuery<User>(key: ['users', 1], fetcher: () => api.getUser(1));
final f2 = client.fetchQuery<User>(key: ['users', 1], fetcher: () => api.getUser(1));
final f3 = client.fetchQuery<User>(key: ['users', 1], fetcher: () => api.getUser(1));
// f1, f2, f3 sont le même Future : un appel réseau, trois attendants
final results = await Future.wait([f1, f2, f3]);
// results = [alice, alice, alice]
La même logique s'applique aux abonnés watchQuery :
// Montés simultanément sur la même page
final stream1 = client.watchQuery<User>(key: ['users', 1], fetcher: () => api.getUser(1));
final stream2 = client.watchQuery<User>(key: ['users', 1], fetcher: () => api.getUser(1));
// Un seul fetch se déclenche ; les deux streams émettent la même séquence Loading → Success
Périmètre de la déduplication
La déduplication s'applique au sein d'une seule instance de QoraClient. Un client partagé unique par application (ou scope DI) fournit une couverture de déduplication complète sur tous les widgets et services.
// Un client partagé : déduplication complète
final client = QoraClient();
class UserRepository {
UserRepository(this._client); // injecté
final QoraClient _client;
}
class PostRepository {
PostRepository(this._client); // même instance
final QoraClient _client;
}
// Deux clients séparés : pas de déduplication entre eux
final clientA = QoraClient();
final clientB = QoraClient(); // cache différent, map pending différente
Interaction avec les retries
La déduplication et les retries se composent correctement. Si la requête en cours échoue, tous les attendants reçoivent le même état Failure après épuisement de toutes les tentatives :
fetchQuery appelé par A, B, C (dédupliqué en 1 requête)
↓ tentative 1 → échoue
↓ attente 1 s
↓ tentative 2 → échoue
↓ attente 2 s
↓ tentative 3 → échoue
↓ Failure stocké en cache
A, B, C reçoivent tous Failure
Quand la déduplication ne s'applique pas
La déduplication fusionne uniquement les requêtes en cours simultanément. Les appels séquentiels sont indépendants et résolus via le cache :
// Séquentiel : deux appels réseau (chacun passe par SWR ou cache indépendamment)
final u1 = await client.fetchQuery<User>(key: ['users', 1], fetcher: api.getUser);
final u2 = await client.fetchQuery<User>(key: ['users', 1], fetcher: api.getUser);
// ↑ servi depuis le cache (vérification staleTime)
Étapes suivantes
- Cache : Comment le cache stocke et fait vieillir les entrées
- Stale-While-Revalidate : Revalidation en arrière-plan
- Mutations et invalidation : Les écritures et leur effet sur le cache
- API QoraClient : Référence complète du client
Stale-While-Revalidate
Le SWR de Qora retourne les données en cache immédiatement et revalide en arrière-plan pour maintenir les UIs réactives et les données à jour.
Mutations et invalidation
Comment s'exécutent les écritures, dans quel ordre leurs callbacks sont appelés, et les trois façons de réaligner le cache sur le serveur ensuite.