ediverse Explore the platform

Spotlight PEPPOL BIS Billing 3.0 The EU e-invoicing mandate is here — France Sept 2026, Belgium Jan 2026, Germany 2025.

State Machine (EDI lifecycle)

The explicit finite automaton — named states, guarded transitions, full journal — making the life of an EDI message traceable and auditable.

Problem

An INVOIC arrives at 10:00, crosses five components (parser, validator, enricher, router, sender), can be ack'd by the ERP at 14:30, rejected at 15:00, reprocessed at 16:00, archived at 17:00. If state is implicit — scattered across DB fields of several services, or in logs — the EDI support operator cannot answer "where is my invoice?". Worse: a bug skips a step (the ERP acks before we routed), and two weeks later we discover invoices stuck without possible transition.

Forces

  • The EDI lifecycle is complex. Six to ten states depending on needs (reception, validation, enrichment, routing, ack, archive).
  • Transitions are constrained. We do not jump from [Received] to [Archived] without validating, enriching, delivering.
  • Auditability is required. Tax compliance, GDPR, partner SLA — we must prove every transition.
  • Error states are real states. Not exceptions to log, but states a message may stay in for days awaiting human action.
  • Temporal aspect. Some transitions must happen within a deadline (CONTRL within 24h, ERP ack within 4h).

Solution

Model the lifecycle as an explicit finite state machine with: named states, allowed transitions (DAG), guards (entry conditions), actions (transition effects), output events (Domain Events emitted). The state machine is persisted: each message has its record with current state, history, timestamps. The transition is atomic with the business effect (via Outbox). Implementations: state tables + optimistic lock, or workflow engine (Camunda, Temporal, AWS Step Functions, Apache Airflow for batch). Strong links with Process Manager and Saga Orchestration.

Inbound INVOIC lifecycle:

   [Received]                ─ AS2 / AS4 message arrived
       │
   syntactic ok
       ▼
   [SyntacticallyValid]      ─ positive CONTRL sent
       │
   semantic ok
       ▼
   [SemanticallyValid]       ─ EN 16931 OK
       │
   business ok
       ▼
   [BusinessValid]           ─ business rules OK
       │
   enriched
       ▼
   [Enriched]                ─ references resolved
       │
   routed
       ▼
   [Routed]                  ─ published on Kafka edi.invoices
       │
   acked by ERP
       ▼
   [AckedByERP]              ─ ERP acknowledged receipt
       │
   processed
       ▼
   [Processed]               ─ booked + paid
       │
       ▼
   [Archived]                ─ stored 10 years S3 Object Lock

   Error states (lateral transitions):
   - [SyntacticallyInvalid]   ← negative CONTRL, technical DLC
   - [SemanticallyInvalid]    ← APERAK, semantic DLC
   - [BusinessRejected]       ← APERAK + business workflow
   - [EnrichmentFailed]       ← retry or degraded
   - [RoutingFailed]          ← retry
   - [ErpRejected]            ← exception flow

EDI implementation

Concrete case: Java EDI hub with Spring State Machine or Akka FSM, or Temporal workflow. Each received INVOIC has a DB row: { messageId, state: 'Received', history: [{state, at, by}], lastActivityAt }. The EDIFACT parser emits a SyntacticallyValid event, the state machine checks the guard ("state == 'Received'") and transitions to SyntacticallyValid. Attempting to skip a step: rejected. If the validator fails at business stage, transition to BusinessRejected, APERAK emitted to the partner, operator workflow triggered. The ops dashboard shows the distribution: "120 invoices Received, 5 BusinessRejected, 2 EnrichmentFailed". Support answers "Your invoice INV-1234 has been in Enriched state since 10:32, next step routing". For French tax audit: the state trace replaces logs to prove no submitted INVOIC escaped archiving. Tools: Temporal (Uber's Cadence fork) industrialises this pattern at scale; BPMN 2.0 in Camunda offers visual modelling.

Anti-patterns

  • Implicit scattered state. Fields is_valid, is_enriched, is_acked in several tables: no transactional consistency.
  • Unguarded transitions. A service can directly jump from Received to Archived: pattern neutralised.
  • In-memory state machine. Crash → state lost → message in limbo.
  • Too many states. 30 microscopic states (parser-1, parser-2, parser-3): unmanageable. Keep business-meaningful granularity.
  • No error state. Errors handled as out-of-machine exceptions: invisible in dashboard, lost in logs.
  • State machine without timers. No detection of stuck messages: discovered 1 month later.

Sources