Every enterprise integration estate trends toward the same shape: hundreds of point-to-point connections, nightly batch jobs nobody dares touch, and an ESB from 2012 that is somehow both deprecated and load-bearing. Event-driven architecture (EDA) is the credible exit — but it's also the pattern most frequently implemented as "we installed Kafka" and least frequently implemented as an architecture. Having built event backbones for banks, logistics networks, and manufacturers, here is what actually separates the systems that scale from the ones that become a distributed version of the same mess.
Events are contracts, not notifications
The foundational decision is what an event is. Three grades, in ascending order of decoupling:
- Notification — "order 4412 changed, call me for details." Cheap to produce, but every consumer must call back, so you've built synchronous coupling with extra steps.
- State-carried event — the event carries the full current state of the entity. Consumers need nothing else; producers absorb the cost of completeness.
- Domain event — a fact, past tense, business-meaningful:
OrderShipped,PaymentCaptured,CreditLimitExceeded. NotOrderUpdated— a fact, not a diff of a database row.
Enterprises should default to domain events with enough carried state that the top consumers don't need callbacks. And because events outlive services, they need the same rigor as any public API: a schema registry, explicit versioning, and a compatibility policy (additive fields fine; renames and semantic changes require a new version with a deprecation window). Teams that treat the event stream as an internal detail rediscover, eighteen months in, that they've published an API to forty consumers without meaning to. The event catalog is your integration API surface — govern it like one.
One naming rule pays for itself endlessly: events describe what happened, never what should happen next. InvoiceApproved, not SendInvoiceEmail. The moment an event encodes its consumer's job, you've re-coupled the systems and lost the ability to add consumers without renegotiation.
The outbox pattern is not optional
The most common correctness bug in event-driven systems: a service writes to its database and then publishes an event as a separate step. Any failure between the two produces either a phantom event (published, transaction rolled back) or a lost one (committed, publish failed). At integration scale, "rarely" happens daily.
The transactional outbox closes the gap: the event is written to an outbox table in the same transaction as the state change, and a relay (CDC via Debezium-style connectors, or a poller) publishes from the outbox to the broker.
BEGIN;
UPDATE orders SET status = 'shipped' WHERE id = 4412;
INSERT INTO outbox (topic, key, payload)
VALUES ('orders.events', '4412', '{"type":"OrderShipped",...}');
COMMIT;
This guarantees at-least-once publication — which brings us to the myth worth killing: end-to-end exactly-once delivery across heterogeneous enterprise systems is not a thing you buy; broker-level exactly-once semantics don't survive contact with the SaaS webhook or mainframe adapter at the end of the chain. The achievable and sufficient contract is at-least-once delivery plus idempotent consumers: every event carries a stable ID, every consumer records processed IDs (or uses natural idempotency like upserts), and duplicates become harmless. Design reviews at Ilmora ask one question of every consumer: "what happens when this event arrives twice?" If the answer isn't boring, it's a defect.
Workflows: sagas, and the choreography ceiling
Long-running business processes — onboarding, order fulfillment, claims — span services, and distributed transactions are off the table. The saga pattern decomposes them into local transactions with compensating actions for rollback (ReservationCancelled compensates InventoryReserved).
The real decision is coordination style. Choreography — each service reacts to events, no coordinator — is elegant to about four steps, after which nobody in the organization can answer "where is order 4412 stuck and why?" without archaeology across five services' logs. Orchestration — an explicit workflow engine or orchestrator service that owns process state and emits commands — centralizes that answer at the cost of a component to operate.
Our rule after many of these: choreography for local, short reactions (2–3 steps, single domain); orchestration for anything cross-domain, long-running, or compensable. Enterprises consistently underrate how much operability — the stuck-workflow dashboard, the retry button, the audit trail per instance — dominates architectural elegance once volume arrives.
Operating the backbone: the unglamorous 60%
What separates production EDA from the conference-talk version:
- Dead-letter queues with an owner. Every consumer has a DLQ, every DLQ has a dashboard, alerting, and a named team. A DLQ nobody drains is a data-loss program with extra steps.
- Replay as a first-class capability. Consumers must be replay-safe (idempotency again), because reprocessing history is how you fix bugs, backfill new consumers, and recover from bad deploys. Compacted topics or an event store make "rebuild this service's state from the stream" a routine operation instead of a heroic one.
- Ordering honesty. Partition-level ordering by entity key is what brokers give you; global ordering is not. Design consumers around per-key order and out-of-order tolerance across keys, or you will discover this in production during a partition rebalance.
- Backpressure and consumer lag SLOs. Lag is the health metric of the whole architecture. Alert on lag trend per consumer group; a consumer falling steadily behind is an outage in progress that no HTTP check will see.
Why this matters more in the AI era, not less
The event backbone you build for integration turns out to be the substrate your AI roadmap needs. Feature pipelines want fresh, ordered, replayable facts — that's a topic. RAG corpora need to know what changed to re-embed incrementally — that's CDC into the event stream. And agentic systems are, structurally, event consumers and command emitters: an agent that reacts to ClaimFiled, gathers context, and emits SettlementProposed slots into an event-driven estate with the same contracts, the same idempotency requirements, and the same audit trail as any other consumer.
Which is the deeper argument for doing EDA properly: events done right are your organization's facts, ordered and replayable. Every future system you build — analytical, operational, or intelligent — is a new reader of the same history. Build the history well.
WRITTEN BY
Daniel Reyes
Chief Technology Officer
Daniel leads Ilmora's engineering organization of 400+ engineers across four continents. A former distributed-systems architect, Daniel writes about platform architecture, agentic systems, and the engineering discipline behind reliable AI in production.
