Un timeout n’indique pas qu’une opération a échoué : il confirme seulement que le client n’a pas reçu de réponse dans le délai imparti. Le serveur peut avoir créé la commande, le prestataire de paiement peut avoir accepté le paiement ou un processus asynchrone peut continuer à s’exécuter. Si le client réessaie sans contrôle, une même intention métier peut produire des effets dupliqués.
L’idempotence en PHP transforme une répétition technique en une consultation ou en le renvoi du résultat déjà obtenu. Il ne s’agit pas d’ignorer tous les doublons ni de se fier uniquement au fait que l’utilisateur ne clique pas deux fois. C’est un contrat explicite entre le client, l’API, la persistance et, le cas échéant, les systèmes externes.
Le problème : la réponse est perdue, mais l’effet demeure

Considérez un endpoint qui confirme un achat. L’application valide la requête, enregistre la commande, demande le paiement et prépare une réponse. La connexion est interrompue juste avant que le client ne la reçoive. Lorsqu’il renvoie le même formulaire, l’endpoint ne peut pas déduire de son contenu qu’il s’agit du même achat : deux commandes avec les mêmes produits peuvent être des intentions valides et distinctes.
Le problème apparaît également lors de créations d’utilisateurs, d’attributions de crédits, d’émissions de documents, de synchronisations, de webhooks et d’actions administratives. Il convient de distinguer trois éléments :
- Intention métier : « je veux confirmer cet achat précis ».
- Requête technique : un envoi HTTP avec des en-têtes, un corps et un contexte d’authentification.
- Tentative d’exécution : chaque traitement interne, nouvelle tentative de file d’attente ou appel à un prestataire.
La clé d’idempotence identifie l’intention, et non une connexion HTTP ni chaque tentative du serveur. Elle doit donc survivre aux nouvelles tentatives réseau et, lorsque le flux l’exige, aux redémarrages du processus.
Quelles opérations nécessitent l’idempotence et lesquelles n’en ont pas besoin
Priorisez les opérations qui créent, confirment, prélèvent, envoient, réservent, notifient ou modifient une ressource avec des conséquences importantes. Un POST /payments, la confirmation d’une commande ou la réception d’un webhook sont des candidats évidents. Il en va de même pour une tâche de file d’attente qui peut être livrée plus d’une fois.
Une lecture pure n’a normalement pas besoin d’une clé d’idempotence. Une mise à jour peut avoir une sémantique différente : définir un état souhaité, tel que PUT /profiles/42, peut être idempotent par conception si la même représentation laisse la ressource dans le même état. En revanche, une action telle que « ajouter du solde » ne l’est pas simplement en raison du verbe utilisé.
Une clé ne doit pas non plus être utilisée comme substitut à d’autres règles. Pour empêcher deux réservations compatibles dans un inventaire limité, il faut des invariants de domaine, un contrôle de concurrence et une politique de réservation. Pour exécuter une tâche une seule fois dans un environnement distribué, la livraison réelle est généralement au moins une fois ; le consommateur doit tolérer les doublons.
Conception de la clé et de l’enregistrement persistant
Le client devrait générer une clé opaque et suffisamment imprévisible lorsque l’intention métier naît, la conserver tant qu’il peut réessayer et l’envoyer, par exemple, dans Idempotency-Key. Si le serveur la génère à chaque réception, il ne pourra pas relier une répétition ultérieure. Dans les flux internes, la clé peut être dérivée d’un identifiant stable de l’événement métier.
Sa portée doit inclure l’acteur ou le tenant et l’opération. La même chaîne ne devrait pas entrer en collision entre deux comptes ni entre « créer une commande » et « émettre un remboursement ». Définissez une rétention alignée sur la période réelle de nouvelles tentatives et sur les risques du domaine. Supprimer l’enregistrement trop tôt rouvre la porte au doublon ; le conserver indéfiniment augmente le coût et exige une politique de confidentialité et de suppression.
Un modèle minimal de persistance comprend :
- la portée de sécurité ou le tenant, le nom de l’opération et la clé d’idempotence ;
- une empreinte cryptographique d’une charge utile normalisée ;
- un état :
processing,completed,failedoupendinglorsque la confirmation externe est incertaine ; - le code et le corps de réponse qui seront renvoyés de manière répétable ;
- les identifiants de la ressource créée, la corrélation interne et la référence du prestataire externe ;
- les dates de création, de mise à jour et d’expiration.
L’empreinte évite une erreur importante : réutiliser la même clé avec des données différentes. Dans cette situation, répondez par un conflit et ne traitez pas la nouvelle charge. Pour que la comparaison soit fiable, normalisez les champs dont l’ordre n’a pas de signification et excluez les métadonnées changeantes qui ne font pas partie de l’intention.
Flux PHP : réserver avant de produire l’effet
La protection doit être soutenue par une contrainte d’unicité en base de données sur la portée, l’opération et la clé. Consulter d’abord puis insérer ne suffit pas : deux requêtes simultanées peuvent constater l’absence de l’enregistrement et poursuivre en même temps.
Le flux recommandé consiste à réserver de manière atomique. Si l’insertion réussit, ce processus est le propriétaire initial de l’exécution. En cas de conflit d’unicité, l’enregistrement existant est lu, l’empreinte est vérifiée et l’action dépend de son état. Un résultat terminé renvoie exactement la réponse persistée ; une opération en cours peut renvoyer un état en attente ou attendre seulement un intervalle limité avant de consulter de nouveau.
begin transaction
insert idempotency_records(scope, operation, key, payload_hash, status)
values (?, 'create_order', ?, ?, 'processing')
-- la contrainte d’unicité décide du propriétaire
commit
if reservation_was_created:
result = execute_business_operation()
persist_completed_response(result)
else:
record = load_existing_record()
assert_same_payload_hash(record)
return replay_or_pending(record)Ne maintenez pas une transaction ni un verrou de ligne ouverts pendant un appel lent à un prestataire. Cela réduit la capacité et peut générer des blocages prolongés. Réservez plutôt et confirmez l’état local dans des transactions brèves. Si l’effet externe et l’enregistrement local doivent être coordonnés, stockez également un ordre d’envoi dans une table transactionnelle et traitez-le séparément. Ce pattern n’élimine pas les nouvelles tentatives, mais permet de récupérer le travail en attente sans perdre l’intention enregistrée.
Concurrence, timeouts et états incertains
Deux requêtes avec la même clé peuvent arriver à quelques millisecondes d’intervalle. La contrainte d’unicité établit laquelle réserve l’opération. La seconde ne doit pas déclencher un autre effet externe. Elle peut répondre 202 tant que l’état est processing ou pending, en incluant un identifiant permettant de consulter le résultat ; si le contrat exige une réponse synchrone, elle peut effectuer une attente limitée et relire l’enregistrement.
Un échec avant le déclenchement de tout effet permet de marquer failed avec une erreur reproductible. Cependant, un timeout lors d’un appel à un système externe crée une incertitude : il n’est pas correct de marquer automatiquement l’opération comme échouée ni de renvoyer un ordre sans autre vérification. Enregistrez la référence de la requête envoyée si elle existe, interrogez le prestataire au moyen de cette référence et rapprochez le résultat. Tant qu’il n’y a pas de confirmation, maintenez pending et indiquez que le résultat n’est pas encore définitif.
L’appel externe nécessite également une référence stable. Si le prestataire accepte sa propre clé d’idempotence, propagez une clé associée à la même intention. S’il ne l’accepte pas, utilisez des identifiants de commerçant, une lecture ultérieure, un rapprochement périodique et des procédures opérationnelles pour les cas ambigus. Aucune transaction locale ne peut rendre atomiques une écriture en base de données et une API distante indépendante.
Ce qu’une clé d’idempotence ne résout pas
L’idempotence évite de répéter une intention reconnue ; elle ne décide pas comment annuler un effet irréversible. Un envoi physique, un virement déjà réglé ou une notification vue par un utilisateur peuvent nécessiter une compensation, une annulation ou une intervention manuelle. Concevez ces actions comme des processus métier explicites, avec des autorisations, des états et un audit.
Ne confondez pas non plus une correction avec une nouvelle tentative. Si l’utilisateur change l’adresse, le montant ou les produits après une erreur, il s’agit d’une nouvelle intention et il doit utiliser une nouvelle clé. Réutiliser l’ancienne avec une autre charge doit produire un conflit, et non mettre silencieusement à jour l’opération d’origine.
Tests, observabilité et liste de contrôle

Testez davantage que le chemin nominal. Interrompez la réponse après avoir persisté le résultat, répétez la même clé en parallèle, redémarrez un worker après avoir réservé l’enregistrement et simulez un timeout après l’envoi d’une requête externe. Vérifiez qu’il n’existe qu’une seule ressource métier, que la réponse répétée conserve le même résultat et qu’une charge différente avec la même clé n’est pas acceptée.
Enregistrez, sans exposer de données sensibles, la clé ou un identifiant sûr dérivé, la portée, l’état, la corrélation et la référence externe. Les métriques de conflits de clés, d’opérations en attente depuis trop longtemps et de rapprochements non résolus aident le support et les opérations à distinguer une nouvelle tentative normale d’un incident.
- La clé représente-t-elle une intention métier et possède-t-elle une portée définie ?
- Existe-t-il une contrainte d’unicité qui empêche deux réservations concurrentes ?
- Une empreinte de la charge est-elle comparée et les changements d’intention sont-ils rejetés ?
- Une réponse ou un résultat pouvant être répété de manière cohérente est-il persisté ?
- Les états incertains permettent-ils de consulter et de rapprocher avant de réessayer ?
- Chaque effet externe dispose-t-il d’une référence, d’une récupération et d’une alternative opérationnelle ?
- Les doublons, pannes, nouvelles tentatives de file d’attente et la concurrence réelle ont-ils été testés ?
Appliquée ainsi, l’idempotence ne promet pas qu’un réseau soit fiable. Elle permet aux défaillances inévitables d’avoir un résultat contrôlable, traçable et cohérent pour l’entreprise.



