ediverse Explorer la plateforme

À la une PEPPOL BIS Billing 3.0 L’obligation européenne d’e-invoicing arrive : France sept 2026, Belgique janv 2026, Allemagne 2025.

Idempotent Sender

L'émetteur qui sait que ses retries ne créeront pas de doublons — le complément indispensable de l'idempotency receiver.

Problème

Un hub EDI envoie un INVOIC à Walmart via AS2. L'envoi part, la connexion timeout après 60s. Walmart a-t-il reçu ? Le hub retent. Walmart répond MDN pour les deux envois : l'ERP de Walmart a deux factures, lance deux paiements identiques. Si on n'a pas mis d'idempotency receiver côté Walmart, l'erreur de timeout côté hub fait payer deux fois. Mais : même si Walmart fait l'effort de dédoublonner, encore faut-il que le hub envoie deux fois la même clé d'idempotence. Un hub qui regénère un MessageId aléatoire à chaque retry produit deux messages « différents » pour Walmart.

Forces

  • At-least-once est la sémantique réaliste. Tout réseau distribué retent en cas de doute.
  • La clé d'idempotence doit être stable au retry. Si elle change, le receiver ne peut pas dédoublonner.
  • La clé doit être persistée avant l'envoi. Si on la regénère à chaque tentative en mémoire, un crash entre deux tentatives détruit la cohérence.
  • Le retry peut être déclenché par un autre nœud. En cluster, le node A envoie, crash, node B retent — il doit retrouver la même clé.

Solution

Avant toute tentative d'envoi : (1) Générer une clé d'idempotence stable et déterministe à partir du domaine : send-{invoiceNumber}-to-{partnerId}, ou ULID persisté avec la facture en base. (2) Persister la clé + un hash du payload + status pending dans une table sender_log en transaction avec la création business. (3) Envoyer le message en transportant la clé dans un en-tête stable : Message-ID AS2, RFF EDIFACT, header HTTP Idempotency-Key (IETF draft) ou X12 BIG02. (4) En cas de retry, lire le sender_log et réutiliser la même clé, le même hash, sans regénération. (5) Le receiver, qui a son idempotency receiver, dédoublonne sur la clé reçue.

Idempotent Sender :

  1. Génération clé    "send-INV-2026-0001234-attempt-1"
     (stable et déterministe, jamais regenerée au retry)
            │
            ▼
  2. Persistance clé + payload en DB    transaction T1
            │                            sender_log {
            │                              key, payload_hash,
            │                              status='pending',
            │                              attempt_count, created_at
            │                            }
            ▼
  3. Envoi AS2 / AS4 / API REST         message porte la clé en
                                        Message-ID / RFF
            │
            ▼  ack reçu → status='delivered'
            ▼  timeout / 5xx → status='pending', attempt++
            ▼
  4. Retry → lookup clé en DB
            si status='delivered' → ne pas renvoyer (idempotent)
            si status='pending'   → renvoyer même message, même clé

Implémentation EDI

Cas concret : hub EDI génère un INVOIC EDIFACT D.96A pour Walmart. Le pattern Outbox stocke en DB : {invoice_id, message_id: "EDI-INV-2026-0001234-v1", payload_hash: "sha256:abc...", status: "pending", attempt: 0}. Le sender lit la queue Outbox, envoie via AS2 avec Message-Id: EDI-INV-2026-0001234-v1@hub.local. Si MDN reçu : status='delivered'. Si timeout : status reste pending, attempt++. Le retry réutilise exactement la même Message-Id. Walmart, qui dédoublonne sur Message-Id (cf. RFC 4130 §10), reconnaît le doublon et n'envoie qu'une seule INVOIC à son ERP. Variante REST : header HTTP Idempotency-Key: EDI-INV-2026-0001234-v1 (cf. IETF draft Stripe, Square). En cas de retry, le serveur retourne la réponse cachée. Pour EDIFACT : utiliser BGM C002 1004 (Document Number) + UNB 0020 (Interchange Control Reference) — les deux doivent être stables au retry. AS4 / ebMS3 utilise eb:MessageId + eb:RefToMessageId pour les duplicate eliminations.

Anti-patterns

  • Clé d'idempotence aléatoire. UUID regénéré à chaque tentative : deux UUID différents = deux messages pour le receiver.
  • Clé en mémoire seulement. Si le service redémarre, la clé est perdue, retry produit un doublon.
  • Pas de hash du payload. On peut vouloir tester qu'un retry envoie le même payload — si modifié entretemps (bug), détecter et alerter.
  • Clé non transportée dans le message. Si le receiver ne voit pas la clé, il ne peut pas dédoublonner.
  • Sender qui ignore les ACK. Si on ne lit pas le MDN/CONTRL/200OK, on retent à l'infini. Toujours coupler acquittements et idempotent sender.

Patterns liés

Sources