Markdown Editor

Event Sourcing and CQRS Design Template

Event-sourced service design: fit check, event catalogue, versioning and upcasting, store, projections, idempotency, crypto-shredding

आर्किटेक्चर और सिस्टम डिज़ाइनमानकeventsourcingcqrs
एडिटर में खोलें.md डाउनलोड करें

उपयोग कैसे करें: Use when a bounded context will store state as events with separate read models. Start with the fit check and the event catalogue, reviewed with domain experts. The main pitfall is deciding versioning, idempotency and personal-data erasure after events are already in production.

प्रीव्यू

Event Sourcing and CQRS Design: [Bounded context or service name]

Design document for a service that stores state as a sequence of domain events (event sourcing) and separates the write model from read models (Command Query Responsibility Segregation, CQRS): when to use it, aggregates and the event catalogue, event schema and versioning, the event store, projections, consistency and ordering, snapshots, replay, idempotency, GDPR erasure and the main interaction flow. Based on common industry practice. Replace every [bracketed] value and every row marked Example; the examples follow a shopping cart and order context.

FieldValue
Document ID[ES-CQRS-001]
Bounded context[e.g. Ordering]
Version[0.1]
Status[Draft / In review / Approved]
Owner[@tech-lead]
Reviewers[@architect], [@data-protection-officer], [@sre]
Last updatedYYYY-MM-DD
Related[Domain model], [Event API specification], [ADR-NNN on event sourcing]

1. When to use and decision

QuestionAnswer for this context
Is a complete, trustworthy history a business requirement?Example: Yes. Customer disputes need the exact sequence of cart and order changes
Do we need to answer "what was the state at time T"?Example: Yes, for pricing audits
Do read and write workloads differ greatly?Example: Yes. 50 reads per write; several read shapes (customer view, warehouse view, analytics)
Is eventual consistency of read models acceptable?Example: Yes, within 2 seconds, except order confirmation which reads its own write
Does the team have experience or support?Example: Two engineers have run an event-sourced system before
Simpler alternative consideredExample: CRUD with an audit table; rejected because audit tables drift from behaviour

Decision: [Use event sourcing with CQRS / Use CQRS only / Do not use]. Recorded in [ADR-NNN].

Avoid this pattern for: [list]. Example: reference data such as the product catalogue and user preferences, which stay CRUD.

2. Aggregates and event catalogue

2.1 Aggregates

AggregateIdentityInvariants enforcedTypical stream length
Example: Cartcart_idMax 100 lines; quantity 1-99 per line; no changes after checkout5-40 events
Example: Orderorder_idCannot ship before payment captured; cancellation only before shipment4-15 events
[Aggregate][ID][Rules][Events]

2.2 Event catalogue

EventAggregateTrigger (command)Key dataSchema versionConsumers
CartCreatedCartCreateCartcart_id, customer_id, currency1Cart view
ItemAddedToCartCartAddItemsku, quantity, unit_price2Cart view, analytics
ItemRemovedFromCartCartRemoveItemsku1Cart view, analytics
CartCheckedOutCartCheckoutcart_id, order_id, total1Order process manager
OrderPlacedOrderProcess managerorder_id, lines, total, shipping_address_ref1Order view, warehouse view, email
PaymentCapturedOrderCapturePaymentpayment_ref, amount1Order view, finance
OrderShippedOrderConfirmShipmentcarrier, tracking_no1Order view, email
OrderCancelledOrderCancelOrderreason_code1Order view, finance, warehouse
[EventName][Aggregate][Command][Fields][n][Consumers]

Naming rules: past tense, domain language, no technical verbs such as "Updated" for a whole aggregate.

3. Event schema and versioning

Every event is stored with a common envelope:

{
  "event_id": "0f8b6c1e-4a1d-4a63-9a7e-2b1f7f0c9d21",
  "stream_id": "cart-6c2a",
  "stream_version": 7,
  "event_type": "ItemAddedToCart",
  "schema_version": 2,
  "occurred_at": "2026-10-21T09:14:03.120Z",
  "correlation_id": "req-91a2",
  "causation_id": "cmd-55f0",
  "tenant_id": "t-001",
  "payload": { "sku": "SKU-1042", "quantity": 2, "unit_price": { "amount": "19.90", "currency": "EUR" } }
}
Change typeAllowed?Approach
Add optional fieldYesConsumers ignore unknown fields; default when missing
Rename or change meaning of a fieldNo in placeNew schema version plus upcaster
Remove a fieldOnly with a new versionUpcaster supplies a derived value or default
Split or merge eventsRarelyNew event types; upcaster or copy-and-transform migration

Upcasting: when an old event is read, a chain of upcasters converts it step by step to the current version before it reaches the aggregate or a projection. Example: ItemAddedToCart v1 stored price as a number in cents; the v1 to v2 upcaster converts it to unit_price with amount and currency taken from CartCreated.

EventFromToUpcasterTest
ItemAddedToCart12Example: ItemAddedV1toV2Golden events from production samples
[Event][n][n+1][Name][Test]

4. Event store

AspectDesign
TechnologyExample: PostgreSQL table events (append-only) with an outbox for publishing; or a dedicated event store product
Stream layoutOne stream per aggregate instance: {aggregate}-{id}
ConcurrencyOptimistic: append succeeds only if expected stream_version matches; unique key on (stream_id, stream_version)
Global orderMonotonic global_position for projections that read all streams
PublishingTransactional outbox to the event broker; at-least-once delivery
RetentionPermanent for business events; see section 9 for personal data
BackupsPoint-in-time recovery; restore tested quarterly
Expected volumeExample: 3 million events per day, average 600 bytes, about 650 GB per year

5. Projections and read models

Read modelPurposeEvents consumedStoreFreshness targetOwner
Example: Cart viewShow current cart to customerCart*, Item*Key-value store< 500 ms[@team-cart]
Example: Order historyCustomer order listOrder*, PaymentCapturedDocument database< 2 s[@team-order]
Example: Warehouse pick listOrders ready to shipPaymentCaptured, OrderCancelled, OrderShippedRelational table< 5 s[@team-fulfilment]
Example: Sales analyticsRevenue by day and productAll order eventsWarehouse< 1 h[@data-team]
[Read model][Purpose][Events][Store][Target][@name]

Each projection records its checkpoint (global_position last processed) in the same transaction as its writes, so restarts resume exactly where they stopped.

6. Consistency and ordering

  • Within an aggregate: strongly consistent. Commands load the stream, check invariants and append with optimistic concurrency.
  • Across aggregates: eventually consistent, coordinated by process managers (sagas) reacting to events. Example: CartCheckedOut triggers the order process manager to issue PlaceOrder.
  • Read models: eventually consistent. Commands return the new stream version; the UI can wait until the read model reaches that version ("read your own writes") for order confirmation.
  • Ordering: guaranteed per stream by stream_version. Across streams only global_position order is guaranteed in the store; the broker partitions by stream_id so per-aggregate order is preserved for consumers.

7. Snapshots, replay and rebuild

TopicDesign
Snapshot triggerExample: Every 100 events for Order streams; none for Cart (short streams)
Snapshot storageSeparate table, keyed by stream and version; may be deleted at any time
Snapshot versioningSnapshot schema version stored; on mismatch, ignore and replay from start
Projection rebuildBuild a new projection version alongside the old one, replay from position 0, switch reads when caught up, then drop the old one (blue-green)
Rebuild durationExample: About 6 hours for one year of events (1.1 billion) at 50,000 events/s
Side effects during replayDisabled: emails, payments and external calls are only triggered by live processing

8. Idempotency

PointMechanism
Command submissionExample: Client sends Idempotency-Key; command handler stores key and result for 24 h
Event appendDeterministic event_id per command; duplicate append rejected by unique constraint
Projection handlerSkip events with global_position at or below checkpoint
External side effectsRecord event_id in a processed-events table before calling out; external API called with the same idempotency key

9. GDPR erasure and personal data

RuleDesign
Keep personal data out of events where possibleExample: Store shipping_address_ref, not the address; the address lives in a CRUD store that supports deletion
Personal fields that must be in eventsEncrypted with a per-customer data key held in a key management service
Erasure (crypto-shredding)Delete the customer's key; events remain but personal fields can no longer be decrypted
ProjectionsErasure event triggers deletion or anonymisation in every read model
BackupsKeys are not in event-store backups, so restored events stay unreadable
EvidenceErasure log with request ID, key ID and timestamp

10. Main interaction flow

The sequence shows a customer adding an item to a cart and the cart view being updated.

11. Operations and testing

AreaPractice
MonitoringExample: Projection lag in seconds and positions; outbox backlog; concurrency conflict rate; upcaster errors
AlertsProjection lag > 30 s for 5 min; outbox backlog > 10,000
Aggregate testsGiven past events, when command, then new events (or error)
Projection testsGiven events, then read model state
Upcaster testsStored golden samples of every historical schema version
Rebuild drillFull rebuild of one projection in staging every quarter

Standard and licence note

Based on common industry practice for event sourcing and CQRS, as described in public writing by Greg Young, Martin Fowler and others. Original text written for this collection; examples are illustrative. The GDPR section describes a technical pattern and is not legal advice. Template licence: MIT.

संबंधित टेम्पलेट