Un utilisateur crée une fiche, clique sur Enregistrer, et l’écran revient presque vide. En base locale, la ligne existe : identifiant, timestamps, champs saisis. Sur l’outil distant — CRM, ERP, canal métier — rien n’apparaît. Le support dit « enregistrez une seconde fois sans rien changer » ; au second clic, la fiche s’affiche. Cette consigne n’est pas une procédure métier : trois mécanismes distincts produisent le même symptôme.
Le problème n’est pas le bouton Enregistrer ni un cache d’interface. C’est que la création et la mise à jour n’empruntent pas le même chemin d’émission vers l’autre système — ou que l’émission part, est rejetée, ou arrive trop tard pour l’écran. Tant qu’on ne départage pas ces causes, on forme les utilisateurs au double clic — et l’on prépare des écrans vides persistants, ou des doublons le jour où la création sera enfin branchée.
Symptômes à documenter avant de toucher au code
Dans une classe fréquente de reprises d’intégration d’outils métiers, le ticket dit « la fiche n’apparaît pas tout de suite », « il faut sauvegarder deux fois », ou « ça marche après quelques minutes ». Ces formulations mélangent déjà des causes. Avant toute hypothèse, on fixe ce que l’on observe sur un scénario illustratif reproductible, sans inventer un incident nominatif.
Le premier enregistrement renvoie un succès (redirect, toast, code 200) et la fiche locale est créée. Dans l’autre outil, la fiche est absente, incomplète, ou encore invisible pour le métier. Au second enregistrement, sans modification de contenu, l’autre écran se remplit. Le délai entre les deux clics — quelques secondes ou plusieurs minutes — change déjà la cause probable. Côté journaux, un booléen « synchronisé » ne distingue ni création ni mise à jour ; un 200 local peut masquer un rejet distant ; une file créée au premier clic peut n’être traitée qu’après le second.
Trois hypothèses, pas une seule
La première hypothèse est la création muette : le chemin de création écrit en base locale et s’arrête — aucun événement sortant, aucun job, ou un payload volontairement minimal. Le chemin de mise à jour, lui, appelle le webhook, reconstruit le DTO distant, pousse les pièces jointes. Le second Enregistrer « marche » parce qu’il emprunte enfin le chemin update, souvent le seul exercé en recette.
La deuxième hypothèse est l’événement de création rejeté. L’émission part au premier clic, mais le distant répond 422, 409, ou accepte HTTP puis écarte en validation interne : champ obligatoire absent à la naissance (numéro métier, identifiant distant, statut), format incorrect, ou ressource distante pas encore provisionnée. Des traitements locaux post-création remplissent ensuite ces champs ; le second clic renvoie un payload plus complet, cette fois accepté. Même symptôme, autre étage. Un 200 local ne prouve pas que l’autre outil possède la fiche — terrain déjà balisé dans l’intégration d’APIs externes.
La troisième hypothèse est la file asynchrone retardée, confondue avec un second clic salvateur. La création locale réussit ; un job est enqueued. La file est saturée, le worker arrêté, ou le traitement met trente secondes. L’utilisateur reclique et attribue le succès au second clic alors que le premier job vient seulement d’aboutir — ou qu’un second job a été lancé. Sans corrélation création / job / résultat distant, on ne sait pas ce que le second clic a changé ; c’est le sujet de la fiabilité des tâches asynchrones. Ces trois hypothèses peuvent coexister : les confondre produit un appel HTTP synchrone dans la requête, ou un job périodique qui resynchronise tout, au risque de doublons.
Tests discriminants : hypothèse, geste, résultat
On ne commence pas par refaire le mapping entier. On sépare les causes avec des gestes courts, mesurables, là où l’on contrôle la file.
Hypothèse 1 — création muette / sync seulement sur update. Test : créer une fiche sans la rouvrir ni la réenregistrer ; attendre le délai maximal annoncé (ou cinq minutes) ; comparer le payload émis à la création avec celui d’une mise à jour (champs, type d’événement, URL). Résultat attendu si l’hypothèse tient : aucun événement de création, ou un payload nettement plus pauvre que l’update ; l’outil distant reste vide. Conclusion : brancher le même builder d’émission sur la création, pas former au double clic.
Hypothèse 2 — événement création rejeté (champ manquant, id distant). Test : rejouer une création seule en capturant la réponse distante et le corps exact ; corréler l’identifiant local avec l’identifiant distant s’il existe ; vérifier les champs absents à T0 qui existent après un traitement local. Résultat attendu si l’hypothèse tient : rejet explicite ou silencieux sur un champ requis ; après enrichissement, un second envoi passe. Conclusion : soit enrichir avant émission, soit émettre un événement métier nommé « lorsque le numéro / l’id distant existe », distinct du bouton Enregistrer.
Hypothèse 3 — file async retardée confondue avec le second clic. Test : créer une fois sans recliquer, mesurer le délai jusqu’à apparition distante ; puis, sur un autre essai, créer et recliquer immédiatement ; comparer les timestamps. Résultat attendu si l’hypothèse tient : l’apparition suit le job, pas le second clic ; le second clic peut lancer un job en double. Conclusion : afficher un état « en cours d’envoi », rendre le job idempotent, et ne pas laisser l’UI suggérer un nouvel enregistrement comme remède.
Ces trois tests tiennent souvent en une demi-journée. Ils collent à des artefacts — payload, HTTP, corrélation, délai — plutôt qu’à un débat d’intention.
Correction maintenant, prévention et non-régression ensuite
La correction traite ce qui bloque aujourd’hui. Si la création est muette, on fait émettre le même contenu utile que l’update dès que les champs existent, via un builder unique appelé aux deux moments. Si le rejet vient d’un champ absent à la naissance, on ne promet plus ce champ dans le premier événement : on envoie un second événement métier nommé quand il apparaît (numéro, PDF, id distant). Si la file retarde, on expose l’état d’envoi (envoyé, rejeté, en attente) et l’on corrige la file — retries, dead letter, supervision — sans demander un second Enregistrer. Les erreurs deviennent lisibles : 422 avec le champ manquant, 409 si la fiche existe déjà, rejet rejouable stocké.
La prévention se pose une fois le chemin création corrigé. On trace création et mise à jour séparément, avec un identifiant de corrélation (entity_id + run_id), pas un booléen « synchronisé ». On documente quels champs existent à T0 et lesquels arrivent plus tard. On n’appelle pas le partenaire en synchrone dans la requête pour masquer une file, ni un job périodique « qui resynchronise tout » pour cacher une création muette.
La non-régression porte surtout sur l’idempotence. Quand la création sera enfin branchée, les utilisateurs continueront souvent de cliquer deux fois par habitude. Le deuxième clic ne doit pas dupliquer la fiche distante ni renvoyer un second « created ». Clé naturelle ou updateOrCreate, hash de payload, job unique par entité, event_id stable : deux clics, un seul enregistrement distant. Sinon l’on a échangé un écran vide contre des doublons.
create → emit(payload_T0) → correlate(local_id, remote_id)
update → emit(payload_Tn) only if hash changed
retry → same event_id / idempotency key
UI → pending | accepted | rejected (never « save again »)
Le test utile n’est pas « create puis show avec la file en synchrone ». C’est create avec file réelle et worker retardé : GET immédiat = état transitoire explicite ; worker qui passe = une seule émission « created » ; second POST identique = zéro second « created » ; échec = message rejouable, pas une invitation à réenregistrer à l’aveugle.
Limites
Ce texte décrit une classe fréquente de dérives création / mise à jour, pas un ticket nominatif. Une projection de lecture abonnée seulement à « updated » peut produire le même écran vide en interne : le réflexe reste hypothèse, test, conclusion. On ne fusionne pas création et mise à jour si le métier a des invariants différents ; on extrait le commun (builder, corrélation). On ne demande pas au métier de « faire attention » : si les équipes enregistrent deux fois pour que la fiche apparaisse, le chemin d’émission est incomplet ou opaque.
Si une recette chez vous n’affiche la fiche distante qu’après un second Enregistrer, le cadre de reprise (création, mise à jour, file, idempotence) est décrit côté expertise Scott-E.
Commentaires 0
Soyez le premier à commenter cet article.
Laisser un commentaire