local-cache-idempotency-fallback

Par divinevideo · divine-mobile

Utilisez un cache en base de données lorsque les API externes ne sont pas fiablement idempotentes. À utiliser quand : (1) l'API externe se prétend idempotente mais renvoie des valeurs différentes pour une même entrée, (2) la ré-exécution d'un script crée des ressources en doublon, (3) des identifiants stables sont nécessaires entre les exécutions alors que le service externe en génère de nouveaux. Schéma : vérifier d'abord le cache en base de données, n'appeler l'API externe que pour les éléments réellement nouveaux, puis mettre le résultat en cache.

npx skills add https://github.com/divinevideo/divine-mobile --skill local-cache-idempotency-fallback

Cache Local comme Fallback d'Idempotence

Problème

Les APIs externes qui devraient être idempotentes (même entrée = même sortie) ne le sont parfois pas. Cela cause des problèmes lors de la réexécution de scripts :

  • Création de ressources dupliquées
  • Les identifiants changent entre les exécutions
  • L'état devient incohérent entre les systèmes

Contexte / Conditions de Déclenchement

  • La réexécution d'un script crée de nouvelles ressources au lieu de trouver les existantes
  • L'API externe a « corrigé » l'idempotence mais retourne toujours des valeurs différentes
  • Besoin d'identifiants stables (pubkeys, user IDs, resource IDs) entre les exécutions
  • Le script fonctionne une fois mais échoue aux exécutions suivantes en raison d'IDs modifiés

Solution

Utiliser le cache de base de données comme source de vérité pour l'idempotence :

// AVANT : Appelle toujours l'API externe (fragile)
const { pubkey, token } = await externalApi.createUser(userId, username);
await db.saveUser({ userId, pubkey, token });

// APRÈS : Vérifier le cache en premier (robuste)
const cached = await db.getUser(userId);
let pubkey: string;
let token: string;

if (cached) {
  // Utiliser les valeurs en cache - stables entre les exécutions
  pubkey = cached.pubkey;
  token = cached.token;
  console.log(`Using cached pubkey: ${pubkey}`);
} else {
  // Appeler l'API externe seulement pour les nouveaux éléments
  const result = await externalApi.createUser(userId, username);
  pubkey = result.pubkey;
  token = result.token;

  // Mettre en cache immédiatement pour la prochaine exécution
  await db.saveUser({ userId, pubkey, token });
  console.log(`Created new pubkey: ${pubkey}`);
}

Motif Clé

  1. Vérifier localement en premier : Toujours interroger votre base de données avant d'appeler l'API externe
  2. Utiliser les valeurs en cache : Si trouvées, utiliser les valeurs locales même si obsolètes
  3. Ne créer que si absent : L'API externe est appelée seulement pour les nouveaux éléments
  4. Mettre en cache immédiatement : Sauvegarder le résultat juste après l'appel API réussi
  5. Logger la source : Indiquer si la valeur est « (cached) » ou « (new) » pour le débogage

Vérification

  • Réexécuter le script plusieurs fois
  • Même identifiant utilisé à chaque fois (depuis le cache)
  • Aucune ressource dupliquée créée dans le système externe
  • Le script est idempotent indépendamment du comportement de l'API externe

Exemple

Application réelle - Création de compte Keycast :

// Vérifier si nous avons un compte en cache en premier (DB local est la source de vérité)
const cached = await db.getImportedUser(creator.user_id);
let pubkey: string;
let token: string;

if (cached) {
  // Utiliser le compte en cache - la pubkey est stable
  pubkey = cached.pubkey;
  token = cached.token;
  console.log(`Pubkey: ${pubkey} (cached)`);
} else {
  // Créer un nouveau compte via l'API externe
  const result = await keycast.createPreloadedUser(
    creator.user_id,
    username,
    displayName
  );
  pubkey = result.pubkey;
  token = result.token;
  console.log(`Pubkey: ${pubkey} (new)`);

  // Mettre en cache le compte en base de données immédiatement
  await db.saveImportedUser({
    vine_user_id: creator.user_id,
    username: creator.username,
    pubkey,
    token,
  });
}

Notes

  • Ce motif fonctionne même quand l'API externe prétend être idempotente
  • Le schéma de base de données doit utiliser l'identifiant d'entrée comme clé primaire (prévient les doublons)
  • Envisager d'ajouter des timestamps pour suivre quand les valeurs en cache ont été créées
  • Pour les systèmes critiques, ajouter une logique de réconciliation pour détecter/corriger la divergence
  • Le cache devient votre source de vérité - le traiter en conséquence

Motifs Associés

  • Upsert on conflict : Utiliser ON CONFLICT DO UPDATE pour gérer les conditions de course
  • Soft delete : Conserver les anciens enregistrements pour suivre l'historique des modifications
  • Cache invalidation : Ajouter un TTL ou un rafraîchissement manuel si les valeurs externes peuvent légitimement changer

Skills Associées

  • stale-cache-external-service-recovery : Que faire quand le service externe perd des données et les identifiants en cache n'existent plus (détecter 404, supprimer le cache, recréer)

Skills similaires