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.

Transactional Outbox (deep dive)

Une variante détaillée : comment dimensionner la table, choisir polling ou CDC, ordonner, garantir l'idempotence et purger sans perdre l'historique.

Problème

Le pattern Outbox de base résout le dual-write (DB + broker). Mais en production réelle, plusieurs sous-problèmes pratiques émergent : Comment ordonner les messages ? Comment dimensionner le poller pour 10k events/s ? Comment purger sans perdre l'audit ? Comment éviter qu'un retry de poller ne re-publie 100 messages déjà sortis ?

Forces

  • Le poller peut être à la traîne — un backlog non-traité accroît la latence end-to-end.
  • Plusieurs instances du poller en HA peuvent doublonner si pas de lock.
  • L'ordre des évènements compte pour certains agrégats (l'OrderConfirmed doit précéder OrderShipped).
  • La table grossit : sans purge, elle dégrade les requêtes applicatives partageant la même base.
  • Un message_id unique est requis pour l'idempotence côté broker.

Solution

L'Outbox détaillé tient en trois rôles distincts : (1) la transaction métier insère dans outbox(id, aggregate_id, payload, status, created_at, headers) ; (2) un relais (polling périodique ou CDC sur le WAL) lit les lignes PENDING et publie ; (3) un purge job archive les lignes SENT plus anciennes que N jours dans un cold storage (S3) puis les supprime. Le relais utilise un SELECT ... FOR UPDATE SKIP LOCKED (Postgres) pour permettre la HA. L'ordre par aggregate_id + created_at est garanti si le broker est partitionné par cette clé.

Schéma de la table outbox

CREATE TABLE outbox (
  id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  aggregate_type TEXT NOT NULL,            -- 'Order', 'Invoice', ...
  aggregate_id TEXT NOT NULL,            -- routing key for broker
  event_type   TEXT NOT NULL,            -- 'OrderConfirmed'
  payload      JSONB NOT NULL,
  headers      JSONB,                    -- correlation, source
  status       TEXT NOT NULL DEFAULT 'PENDING'
                          CHECK (status IN ('PENDING','SENT','FAILED')),
  created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  sent_at      TIMESTAMPTZ,
  attempts     INT NOT NULL DEFAULT 0,
  next_attempt_at TIMESTAMPTZ
);
CREATE INDEX outbox_pending_idx ON outbox (status, next_attempt_at)
  WHERE status = 'PENDING';
CREATE INDEX outbox_agg_idx ON outbox (aggregate_id, created_at);

Polling vs CDC

Deux familles de relais existent :

  • Polling — un job lit SELECT * FROM outbox WHERE status = 'PENDING' AND next_attempt_at <= NOW() FOR UPDATE SKIP LOCKED LIMIT 100 toutes les 1-2 s. Simple, mais latence ≥ intervalle. Utilisé par les hubs EDI modestes (≤ 100 msg/s).
  • CDC — Debezium ou un connector Kafka lit le WAL Postgres / binlog MySQL en push. Sub-second latency. La complexité opérationnelle augmente (replication slot, lag monitoring). Standard chez Walmart, Stellantis, Vinted pour les hubs > 1 000 msg/s.

Implémentation EDI

Cas type d'un hub EDI : l'EDIFACT INVOIC arrivant en AS2 est persisté en DB ; dans la même transaction, on insère un record outbox { eventType: 'EdiMessageReceived', payload: { unbRef, sender, recipient, raw: claim_check_url }, headers: { correlationId } }. Debezium publie sur le topic edi.inbound.received. Un consommateur transformation traduit l'INVOIC en JSON canonique ; un consommateur archive stocke en S3 immuable pour l'audit fiscal 10 ans. Aucun risque que la confirmation AS2 (MDN) soit envoyée sans que le hub interne ait journalisé.

Anti-patterns

  • Outbox dans la même base que l'application sans isolation — un long poller verrouille les écritures applicatives.
  • Pas de FOR UPDATE SKIP LOCKED en HA — deux pollers publient le même message.
  • Pas d'aggregate_id dans le partitionnement broker — l'ordre se perd.
  • Purge directe sans archive — l'audit fiscal n'est plus reconstituable.
  • Réutilisation du id outbox comme message_id sans suffixe attempt — un message déjà SENT puis reset PENDING duplique côté broker.

Patterns liés

Sources