|$ curl https://forge-ai.dev/api/markdown?path=docs/architecture/event-driven
$cat docs/event-driven-architecture.md
updated Today·22-30 min read·published

Event-Driven Architecture

ArchitectureEDAMessagingAdvanced🎯Free Tools
Introduction

Event-driven architecture (EDA) builds systems that communicate by emitting and reacting to events — facts about something that happened — rather than only through synchronous request/response. Producers and consumers decouple in time and space, enabling fan-out, resilience to consumer downtime, and independent scaling.

EDA is powerful and easy to misuse. Adopt it when requirements need decoupling, fan-out, or durable integration — not because "async" sounds modern. Pair with System Design → Messaging for broker technology details.

Events vs Commands vs Queries
MessageIntentNaming
EventSomething happened (fact)OrderPlaced, PaymentCaptured
CommandDo this work (intent)CapturePayment, ShipOrder
QueryReturn dataGetOrder, SearchProducts

Events are past tense and should not tell consumers how to react. Commands target a specific owner capable of performing an action. Confusing them creates hidden coupling (events that are really RPCs).

best practice

Include enough data for consumers to react without synchronous callbacks — but avoid mega-payloads that force constant schema churn. Reference IDs + essential attributes is a common balance.
Requirements That Need EDA
RequirementEDA fit
Many consumers react to one business factStrong — pub/sub fan-out
Producers must not wait on consumersStrong — temporal decoupling
Audit/integration stream for other systemsStrong — durable log
User needs immediate strongly consistent read of all projectionsWeak — sync may be simpler
Simple CRUD single appUsually overkill
  • Why: reduce coupling between bounded contexts; absorb load spikes; integrate SaaS and analytics without blocking core paths.
  • How: define domain events at module/service boundaries; publish via outbox; consume idempotently; document consistency lags in UX.
Brokers & Topologies

Queue (competing consumers)

Commands/work distribution; each message processed by one worker.

Pub/sub topics

Events to many independent subscribers.

Log/stream (e.g., Kafka-style)

Durable ordered log; replay; multiple consumer groups.

Choose based on retention, ordering, replay, and throughput NFRs. The broker is an adapter in hexagonal terms — domain should not be stuffed with vendor client code.

Eventual Consistency

Consumers update their own state asynchronously. The system converges over time. Product and UX must acknowledge this: show pending states, avoid assuming immediate cross-service reads, and define reconciliation processes.

ChallengePractice
Duplicate deliveryIdempotent consumers; dedupe keys
Out-of-orderVersion vectors; ignore stale; per-key ordering
Lost messagesOutbox + durable broker; monitoring lag
Poison messagesDLQ + alerting + replay tooling

warning

If stakeholders require immediate global consistency for a workflow, either keep it in one transactional boundary or invest heavily in orchestration and user communication — do not pretend naive EDA is ACID.
Choreography vs Orchestration
ApproachHow it worksBest when
ChoreographyServices react to each other's eventsSimple fan-out; few steps
OrchestrationA coordinator sends commands/stepsComplex workflows; visibility needed

Choreography can become a "spiderweb" that is hard to reason about. Orchestrators (workflow engines) centralize state machines but add a critical component. Choose deliberately and document the saga in an ADR.

How to Introduce EDA
  • Start with in-process domain events inside a modular monolith.
  • Add outbox + broker when crossing process boundaries.
  • Standardize event envelope (id, type, time, correlation, payload version).
  • Build consumer lag dashboards and DLQ runbooks before wide adoption.
  • Version events compatibility-first; never silently reuse event type names.
event-envelope.json
JSON
1{{
2 "id": "01HZX...",
3 "type": "order.placed.v1",
4 "time": "2026-07-30T12:00:00Z",
5 "source": "checkout",
6 "correlationId": "req_123",
7 "data": {{ "orderId": "ord_9", "total": 4200, "currency": "USD" }}
8}}
Common Mistakes
  • Using events as synchronous RPC with hidden request/reply coupling.
  • Shared database plus events — dual source of truth without rules.
  • No idempotency — random double charges under retry.
  • Unbounded event payloads with PII and no retention policy.
  • Choreography with no tracing — impossible incident response.
EDA Checklist
eda-checklist.txt
TEXT
1[ ] Requirements cite decoupling/fan-out/integration needs
2[ ] Events vs commands distinguished
3[ ] Consistency UX agreed with product
4[ ] Outbox/inbox or equivalent reliability
5[ ] Idempotent consumers + DLQ
6[ ] Schema/version strategy
7[ ] Tracing correlation IDs end-to-end
8[ ] ADR written
Testing Event-Driven Systems
  • Unit-test producers: assert event payloads for given domain actions.
  • Consumer contract tests: sample events from schema registry/fixtures.
  • Idempotency tests: deliver same event twice.
  • Ordering tests where per-key order is promised.
  • End-to-end journey tests with embedded broker in CI for critical paths.
Operating EDA

Monitor consumer lag, DLQ depth, poison rates, and publish failures. Alert on lag SLO breaches. Practice replay drills. Treat schema changes like API changes — compatibility checks in CI.

Ops concernControl
Lag spikeAutoscale consumers; backpressure; check slow handlers
DLQ growthRunbook; quarantine; fix; replay
Schema breakCompat tests; expand/contract versions
Example: Order Flow
order-eda.txt
TEXT
1Checkout publishes OrderPlaced
2 → Inventory reserves stock
3 → Notify emails receipt
4 → Analytics updates funnel
5 → Fraud scores order asynchronously
6Payments publishes PaymentCaptured / PaymentFailed
7 → Checkout updates order status
8 → Notify accordingly
Schema Evolution

Treat events as public APIs. Prefer additive changes; use explicit versions in type names (order.placed.v2); run compatibility checks in CI; give consumers time to adopt (expand/contract).

Change typeSafe approach
Add optional fieldUsually compatible
Remove fieldDeprecate first; wait for consumers
Change meaning of fieldNew event version — never silent
Split eventPublish both during transition

Community

Get help on Slack, Discord or VIP

Stuck on a guide? Join the community and ask.