Event Sourcing
Store the complete history of business facts, not current state — and accept that state is just a projection of the log.
Problem
EDI naturally produces a stream of business events (ORDERS, ORDRSP, DESADV, INVOIC, REMADV) chained over time. A system modelled as CRUD ("update the order to shipped") loses this fine-grained traceability and makes audit, replay and incident investigation difficult.
Forces
- Audit is mandatory in EDI — 10-year fiscal proof, customs compliance, regulatory traceability. Storing the raw history is a feature, not a burden.
- Business events are the truth. State is just a local cache, reconstructible at will.
- Replay enables model evolution. When the schema changes, we replay history to rebuild the new projection.
- Storage cost is low. A compressed business-event log costs an order of magnitude less than an equivalent denormalised data lake.
Solution
Every business fact derived from an inbound or outbound EDI message is persisted as an immutable event in an append-only log (Kafka, EventStoreDB, append-only PostgreSQL table). The current state of an entity (order, shipment, invoice) is computed by replaying its events. Projections are materialised into read-side tables (read models) optimised per use case — operations console, partner API, BI data lake each draw their view from the same source of truth.
# event-log topic
2026-05-14T08:31Z OrderReceived { orderId: ORD789, lines: [...] }
2026-05-14T08:42Z OrderAcknowledged { orderId: ORD789, status: "accepted" }
2026-05-15T07:11Z ShipmentDispatched { orderId: ORD789, asn: ASN9001 }
2026-05-15T15:02Z GoodsReceived { orderId: ORD789, asn: ASN9001 }
2026-05-15T17:30Z InvoiceIssued { orderId: ORD789, invoice: INV-7811 }
2026-05-20T09:00Z PaymentRemitted { orderId: ORD789, invoice: INV-7811, amount: 1240.00 } EDI implementation
Typical EDI hub case: every inbound message is translated into a
business event (OrderReceived, ShipmentDispatched, InvoiceIssued, PaymentRemitted) published to a Kafka topic edi.events.orders partitioned by orderId.
The ERP projector consumes it and materialises an orders
table; the audit projector feeds a fiscal index; the partner
projector publishes a JSON status to a REST API. On incident ("I lost
an order"), replaying the topic reconstructs the state at any point
in time — impossible with a classic CRUD.
Anti-patterns
- Storing state in the event. The event must carry the fact, not the projection. Embedding full state defeats the benefit.
- No event versioning. When the schema changes after
18 months, replay becomes impossible: an
eventVersionis required from sprint one. - Aggressive compaction. Deleting old events loses historic audit ability — forbidden in fiscal EDI.
- Projector / event store coupling. The projector must be addable, removable, replayable without impact on the source of truth.
- Event Sourcing everywhere. For a small static partner directory, CRUD remains simpler. ES applies to entities with a rich lifecycle, not to reference tables.
Related patterns
- CQRS — Event Sourcing is almost always paired with CQRS.
- Message Store — the EIP equivalent applied to the journal.
- Change Data Capture — the non-intrusive alternative for legacy systems that cannot be rewritten.
Sources
- Fowler M. — Event Sourcing, martinfowler.com, 2005. martinfowler.com/eaaDev/EventSourcing.html
- Young G. — CQRS Documents, 2010. The canonical companion to Event Sourcing. cqrs_documents.pdf
- Kleppmann M. — Designing Data-Intensive Applications, O'Reilly 2017, chap. 11 on log-based systems.
- EventStoreDB documentation — the reference open-source implementation. eventstore.com/event-sourcing