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.

Event Sourcing avec journal EDI append-only

Plutôt que de muter une ligne orders.status='SHIPPED', on persiste un événement OrderShipped immuable dans un journal append-only — l'état courant devient une projection rejouable, et l'historique légal est natif.

Problème

Le flux EDI typique (ORDERS → ORDRSP → DESADV → INVOIC → REMADV) est intrinsèquement une séquence d'événements métier qui se passent dans le monde réel. Stocker seulement l'état courant (table orders en CRUD) détruit l'information historique : pourquoi le statut est-il SHIPPED ? À quelle heure précise l'INVOIC a-t-il été acquitté ? Quelle version du mapping a généré ce DESADV ? Quand l'audit fiscal demande des preuves 10 ans après, on n'a plus que la dernière ligne. Pire, en cas de saga compensée, l'état mutable rend impossible de reconstruire la version intermédiaire pour comprendre ce qui s'est passé.

Forces

  • Conformité fiscale et légale : le code des impôts français et l'EU eIDAS imposent une preuve immuable conservée 10 ans (factures, attestations).
  • Audit forensique : en cas de litige partenaire (« vous n'avez jamais reçu mon ORDERS »), il faut prouver l'enchaînement précis des événements.
  • Replay et debugging : pouvoir rejouer une saga complète dans un environnement de staging avec les événements exacts de production.
  • Projections multiples : l'état courant n'est plus unique — on peut matérialiser différentes vues sans collision.
  • Coût stockage : un journal append-only croît linéairement (~5-50 GB par million de messages selon richesse du payload).

Solution

Définir un set restreint d'événements métier (typiquement 20-50 par bounded context EDI), chacun immuable, daté, versionné, et porteur du payload minimal décrivant ce qui s'est passé. Le journal est le système de référence : aucune update, aucun delete. Les agrégats reconstruisent leur état en consommant séquentiellement les événements qui les concernent (par aggregate_id). Les vues read-side sont des projections matérialisées asynchrones (cf. CQRS). Pour les agrégats lourds (commandes avec milliers d'événements), on insère des snapshots périodiques.

Structure

Domain events EDI (extrait):
┌─────────────────────────┬────────────────────────┐
│ MessageReceived         │ AS4 ingestion          │
│ MessageDecoded          │ EDIFACT/UBL parsé      │
│ MessageValidated        │ Schématron OK          │
│ MessageMapped           │ Canonique généré       │
│ OrderReceived           │ Commande métier créée  │
│ OrderConfirmed          │ ORDRSP envoyé          │
│ ShipmentDispatched      │ DESADV envoyé          │
│ InvoiceIssued           │ INVOIC envoyé          │
│ InvoiceAcked            │ REMADV reçu            │
│ MessageRejected         │ ContractViolation      │
│ SagaCompensated         │ Étape annulée          │
└─────────────────────────┴────────────────────────┘

Stream par agrégat:
order-2026-12345 → [
  OrderReceived(t=10:01:23),
  OrderValidated(t=10:01:24),
  OrderConfirmed(t=10:02:11),
  ShipmentDispatched(t=14:33:09),
  InvoiceIssued(t=18:45:01),
  InvoiceAcked(t=2026-05-21:09:12:55)
]

Implémentation EDI

Trois choix de stockage en 2026 : (1) PostgreSQL append-only (table simple avec contraintes NO UPDATE via row-level security ou triggers), simple à opérer, requêtable en SQL. (2) Kafka log compacté avec rétention infinie sur S3 via tiered storage, parfait pour streaming temps réel mais difficile à requêter ad-hoc. (3) EventStoreDB, base spécialisée avec API par stream et projections natives — idéal pour event-sourced DDD pur.

-- Schema PostgreSQL append-only EDI
CREATE TABLE edi_event_journal (
  event_id          UUID PRIMARY KEY,
  aggregate_id      VARCHAR(80) NOT NULL,
  aggregate_type    VARCHAR(40) NOT NULL,   -- 'Order', 'Shipment', 'Invoice'
  sequence_no       BIGINT NOT NULL,        -- ordre par agrégat (1, 2, 3, ...)
  event_type        VARCHAR(80) NOT NULL,
  payload           JSONB NOT NULL,
  metadata          JSONB NOT NULL,         -- correlation_id, user, source_msg
  schema_version    SMALLINT NOT NULL,
  occurred_at       TIMESTAMPTZ NOT NULL,
  recorded_at       TIMESTAMPTZ DEFAULT now(),
  UNIQUE (aggregate_id, sequence_no)
);

-- Empêcher updates/deletes (row-level)
REVOKE UPDATE, DELETE ON edi_event_journal FROM PUBLIC;

-- Rebuild d'un agrégat
SELECT event_type, payload, occurred_at
FROM edi_event_journal
WHERE aggregate_id = 'ORDER-2026-12345'
ORDER BY sequence_no;

Pour l'archivage fiscal, partitionner la table par mois et déplacer vers un stockage froid (S3 Glacier, Azure Archive) après 13 mois. Les événements y restent accessibles via Athena/BigQuery External Tables. La signature cryptographique (XAdES, eIDAS) des événements à la frontière garantit la non-répudiation et l'intégrité durable.

Anti-patterns

  • Stocker le message EDI brut comme « événement » — un événement doit être une intention métier, pas un blob technique. Stocker l'EDIFACT à part dans un object store.
  • Versioner les événements en place (UPDATE) — détruit la propriété append-only et invalide les projections.
  • Pas de schema_version dès le jour 1 — quand la définition d'un événement change (renommer un champ), impossible de rejouer les anciens.
  • Confondre événement et commande — OrderShipped est un événement (passé, fait), ShipOrder est une commande (intention non encore réalisée).
  • Migrer en mode « big bang » d'un système CRUD vers event sourcing — préférer une migration progressive bounded context par bounded context.

Patterns liés

Sources

  • Fowler M. — Event Sourcing, martinfowler.com, décembre 2005. La page fondatrice. martinfowler.com/eaaDev/EventSourcing.html
  • Young G. — Versioning in an Event Sourced System, Leanpub 2017. Le manuel de référence sur la gestion des versions d'événements.
  • Vernon V. — Implementing Domain-Driven Design, Addison-Wesley 2013, chap. 8 (« Domain Events »).
  • Stopford B. — Designing Event-Driven Systems, O'Reilly 2018. confluent.io
  • Règlement eIDAS 910/2014 — Annexe I sur les signatures électroniques avancées requises pour l'archivage légal des documents B2B. eur-lex.europa.eu