CQRS, Event Sourcing, Outbox, and Sagas
The database chapter supplied local transactions, durable identities, and versioned projections. This stage decides how much architectural separation those mechanisms should support. CQRS means command/query responsibility segregation: separating changes from reads. Event sourcing changes the authoritative storage model; an outbox preserves publication intent; a saga coordinates local transactions across owners. Read the spectrum first, compare the three workloads, and then follow the operating contract for each pattern.
Evidence notation: C identifies a claim in the claim register, A a dated AWS source, F a foundational source, and CS a finding in the repository case study. The source index supplies the full source details. These labels are lookup aids, not facts to memorize.
The senior design move is not “add CQRS.” It is to name the authoritative business state, measure why the read and write paths differ, and add only the separation whose freshness, ordering, recovery, and cost can be operated. CQRS does not require two databases. CQRS does not require event sourcing. Event sourcing, transactional outbox, and sagas solve different problems and can be adopted independently (C35,C97; F06,F36; A113, retrieved 2026-08-22).
This chapter consumes the authority and guarantee vocabulary from event-driven foundations, the service boundaries from the AWS messaging guide, the ordering/replay/backlog limits from the Kinesis deep dive, and AP12 ledger/risk authority, AP07 derived portfolio, AP16 semantic outbox, inbox/version/gap/rebuild, and amplification boundaries from the DynamoDB deep dive. It does not turn a transport, cache, search index, portfolio, or analytics view into financial authority.
CQRS is a five-step spectrum
Section titled “CQRS is a five-step spectrum”The archived compilation of material attributed to Greg Young separates command intent from query information, while Fowler warns that CQRS adds risky complexity when applied indiscriminately. AWS describes separate command/query models and possible database combinations; that is one implementation family, not a requirement (C35,C97; F06,F36; A113, retrieved 2026-08-22).
| Step | Separation | State shape | Consistency and recovery | Earn it when | Stop when |
|---|---|---|---|---|---|
| 1 | Separate code paths | Command handlers and query handlers use one domain/state model and one store. | Queries can use the same committed state; ordinary backup/restore applies. | Different authorization, validation, or API contracts make code clearer. | One CRUD path is clearer and no policy differs. |
| 2 | Same-store command/query models | Different DTOs, SQL/DynamoDB access paths, or base-table item shapes share one authority. | Same transaction domain is possible; indexes/replicas still carry their documented lag. | Measured query shape differs but another data pipeline is not justified. | Index/write amplification or query coupling misses the SLO. |
| 3 | Authoritative store + projection | Commands commit authoritative state; an outbox/CDC relay builds one rebuildable view. | Eventual freshness, version/inbox controls, full rebuild, and reconciliation become mandatory. | A measured read pattern cannot meet latency/capacity/isolation goals on authority. | Lag or rebuild risk is unacceptable for the business decision. |
| 4 | Specialized read stores | Separate projections serve search, cache, portfolio, and analytics access patterns. | Each store has its own freshness, overload, schema, rebuild, and cutover contract. | Distinct query engines produce enough avoided work to pay fan-out and operations. | Polyglot ownership and drift cost exceed avoided read work. |
| 5 | Event-sourced command model | The authoritative command state is an append-only per-aggregate event sequence; current state and reads are derived. | Expected-version append, snapshots, evolution, replay isolation, store recovery, and temporal semantics are first-class. | Authoritative history, temporal reconstruction, or domain transition evidence earns it. | CRUD state plus an audit/ledger/outbox meets the requirement more safely. |
- Step
- 1
- Separation
- Separate code paths
- State shape
- Command handlers and query handlers use one domain/state model and one store.
- Consistency and recovery
- Queries can use the same committed state; ordinary backup/restore applies.
- Earn it when
- Different authorization, validation, or API contracts make code clearer.
- Stop when
- One CRUD path is clearer and no policy differs.
- Step
- 2
- Separation
- Same-store command/query models
- State shape
- Different DTOs, SQL/DynamoDB access paths, or base-table item shapes share one authority.
- Consistency and recovery
- Same transaction domain is possible; indexes/replicas still carry their documented lag.
- Earn it when
- Measured query shape differs but another data pipeline is not justified.
- Stop when
- Index/write amplification or query coupling misses the SLO.
- Step
- 3
- Separation
- Authoritative store + projection
- State shape
- Commands commit authoritative state; an outbox/CDC relay builds one rebuildable view.
- Consistency and recovery
- Eventual freshness, version/inbox controls, full rebuild, and reconciliation become mandatory.
- Earn it when
- A measured read pattern cannot meet latency/capacity/isolation goals on authority.
- Stop when
- Lag or rebuild risk is unacceptable for the business decision.
- Step
- 4
- Separation
- Specialized read stores
- State shape
- Separate projections serve search, cache, portfolio, and analytics access patterns.
- Consistency and recovery
- Each store has its own freshness, overload, schema, rebuild, and cutover contract.
- Earn it when
- Distinct query engines produce enough avoided work to pay fan-out and operations.
- Stop when
- Polyglot ownership and drift cost exceed avoided read work.
- Step
- 5
- Separation
- Event-sourced command model
- State shape
- The authoritative command state is an append-only per-aggregate event sequence; current state and reads are derived.
- Consistency and recovery
- Expected-version append, snapshots, evolution, replay isolation, store recovery, and temporal semantics are first-class.
- Earn it when
- Authoritative history, temporal reconstruction, or domain transition evidence earns it.
- Stop when
- CRUD state plus an audit/ledger/outbox meets the requirement more safely.
Step 5 is not “more mature” than Step 3. It changes authority and failure recovery. A team can use event sourcing without a specialized read database, or CQRS with ordinary CRUD state.
Inference: complexity is earned only by measured access patterns, consistency/freshness SLOs, recovery objectives, and total operating cost—not by a pattern ladder (C35,C48,C97; F06,F19,F20,F36).
CQRS spectrum operational contract
Section titled “CQRS spectrum operational contract”Problem / why. One model can force command invariants and high-volume query shapes to compete for code, capacity, schema, and release cadence. Separation earns its place only when traces show material read work, latency, or isolation that the simpler step cannot meet (C35,C48,C97,C102; F06,F19,F20).
Guarantee scope and stopping boundary. Steps 1–2 can keep one committed state boundary. Steps 3–5 introduce derived freshness and recovery obligations; none changes the named command authority or makes a cross-domain query an atomic snapshot. CQRS never supplies delivery, order, or one-time external effects by itself (C35,C41,C46,C97; F01,F06).
Concrete workload. The validated input/derivation table in CQRS break-even model is the concrete economic case; the repository variants separately use their visible peak and query workloads rather than treating the economic ratio as production measurement (C49,C102; F19,F20).
Fit and poor fit. Fit is a measured SLO/cost/isolation miss with an owned projection contract and positive sensitivity. Poor fit is low volume that cannot amortize ownership, strongly current monetary queries, or a team unable to rebuild and reconcile; remain at the lowest sufficient spectrum step.
Failure and recovery. A read-side outage must not stop authoritative
commands: return a versioned receipt/pending state, shed projection-dependent
features, repair gaps, rebuild vNext, reconcile, and cut over. If commands
depend on the view, the split has crossed its stopping boundary (C44,C48,C100;
F13,F14).
Variable cost. Count transport, every projection/index write, query/store compute, bytes and retention, cache/search nodes, replay bandwidth, and reconciliation work per write/read—not merely the cheap serving read (C49,C95, C102; A45,A46,A78, retrieved 2026-08-22).
Fixed ownership. One named team owns projection schemas, compatibility, lag/error alarms, rebuild tooling, reconciliation evidence, capacity tests, runbooks, security, and on-call. This fixed burden exists even during a quiet month.
Metric, threshold, response. Measure command p99, query p99, read work avoided, projection p95/max age, gap age, rebuild RTO, discrepancy count, and CU net benefit. At 30 seconds of projection age or any unresolved monetary break, disable projection-dependent actions, fall back to authority/pending, isolate repair, and page the owner (C43,C48,C68; A36, retrieved 2026-08-22).
Anti-pattern to reject. “CQRS means two eventually consistent databases,” or choosing Step 4/5 before measuring read work and recovery ownership.
Follow-up questions. Which query/SLO fails at the current step? What is the authority during lag, how does read-your-writes work, and who proves a rebuild?
Two-minute defense. “I start with separate command/query code against one authority, then move only the measured read path. The projection is stale and rebuildable, so the receipt carries a version, overload degrades reads rather than invariants, and the extra store must beat transport, fan-out, recovery, reconciliation, and on-call cost under sensitivity.”
CQRS and event sourcing are independent
Section titled “CQRS and event sourcing are independent”CQRS asks whether command and query responsibilities use different models. Event sourcing asks whether the authoritative state is a replayable event sequence. These choices form a two-dimensional matrix:
| Command authority | Same query model | Separate query model |
|---|---|---|
| Current-state CRUD | Ordinary service, perhaps code-path separation | CQRS with same-store DTOs or asynchronous projections |
| Event sequence | Reconstruct aggregate and answer limited queries directly | Event-sourced command model plus one or more projections |
- Command authority
- Current-state CRUD
- Same query model
- Ordinary service, perhaps code-path separation
- Separate query model
- CQRS with same-store DTOs or asynchronous projections
- Command authority
- Event sequence
- Same query model
- Reconstruct aggregate and answer limited queries directly
- Separate query model
- Event-sourced command model plus one or more projections
Publishing OrderAccepted after updating an order row is event notification or
event-carried state transfer; it is not event sourcing. Conversely, an
event-sourced aggregate can initially serve a small query from its reconstructed
state without a second database (C36,C58,C98; F07,F29,F30,F36; A114, retrieved
2026-08-22).
Inference: the repository starts at CQRS-lite: accepted order state, AP12 cash/securities reservations and ledger postings, execution state, and risk limits are authoritative; AP07 portfolio, search, cache, notification, and analytics are rebuildable projections (C46,C91,C97; F08,F17,F21). A strong read does not promote a projection to authority. Strongness describes a store/API read option, not the business provenance of the data (C82,C92; A19,A32,A34,A35, retrieved 2026-08-22).
Architecture contract shared by all variants
Section titled “Architecture contract shared by all variants”The spectrum describes possible separation, not a progression that every system should climb. The variants below compare different workloads using the same questions: who decides, how stale may reads become, what does a duplicate do, and how is a complete rebuild proved? AP references point back to the DynamoDB access patterns; they keep those authority and cost decisions consistent across the designs.
The machine-readable block is the source for the rendered table. Every variant must state authority, lag/overload, order, read-your-writes, duplicate handling, rebuild, recovery/reconciliation, variable cost, fixed operational cost, poor fit, and quantified workload.
These fields preserve the reviewed ordering/backlog, authority, projection, rebuild, and economic boundaries (C41,C44,C46,C49,C95,C100,C102; F13,F14,F19,F20; A36,A45,A46, retrieved 2026-08-22).
Model details · task7 variants
VARIANT|CQRS-lite|accepted order, AP12 ledger/reservation, and risk state|AP07 portfolio p95 under 3 s; at 30 s stop projection-dependent UX and fall back to authority or pending status|per orderId transition and per accountId ledger sequence; no global order|return command receipt with orderId and version; strong base read or wait up to 2 s for projectionVersion at least receipt version|transactional inbox eventId plus conditional next aggregateVersion; duplicate returns stored receipt|PITR export plus outbox/event catch-up into versioned AP07 namespace; control totals then blue-green cutover|repair AP16 outbox and parked gaps; reconcile orders, fills, postings, reservations, and AP07 by batch|DynamoDB transaction and GSI units; Streams/Lambda relay; projection reads/writes; export and replay|projection ownership, alarms, schema/upcaster, rebuild drills, reconciliation and on-call|commands that must decide from AP07 or workload stays inside one store SLO|5,000 accepted orders/s planning peak; 2,000 AP06 queries/s; one portfolio projectionVARIANT|Read-heavy brokerage|order, execution, AP12 ledger/reservation, and risk command stores|portfolio/search/cache p95 under 2 s normal and under 30 s recovery; freeze stale widgets, serve receipt overlay, and shed analytics before authority|per accountId or orderId source version; Kinesis partition key preserves only its documented shard scope; cross-key facts reconcile|receipt overlay and minVersion query token; wait up to 2 s then return pending or read authoritative API|one inbox eventId and sourceVersion condition per projection; stale and duplicate no-op, gaps park|export authoritative domains to S3 manifest; isolated portfolio-vNext, OpenSearch-vNext, and cache warm; catch up and cut over separately|SQS DLQs and retained Kinesis/S3 replay feed isolated rebuild; reconcile counts, money totals, versions, and search coverage|transport fan-out; four projection/index writes; OpenSearch, ElastiCache, S3 and query compute/storage|four-store ownership, schema compatibility, security, lag/on-call, game days and capacity reservations|read/write ratio below modeled threshold or customer action needs strongly current cross-domain state|1,000 commands/s peak; 50,000 reads/s peak; 4 projections; 2 s normal freshness and 30 s overload budgetVARIANT|Write-heavy execution stream|accepted order and reservation/risk state before execution; matcher and authoritative execution/ledger records after execution|status projection p95 under 5 s and hard alarm at 60 s; preserve ingestion by pausing analytics, rate-limit low-priority commands, and drain within retention|instrumentId sequencing lane for matcher and accountId or orderId versions around it; no cross-shard total order|command receipt and authoritative status endpoint; never infer acceptance or fill from stream position|producer eventId plus authoritative expected version; projector inbox; deterministic fill identity and effect receipt|retain authoritative executions/outbox beyond Kinesis window; bulk load compacted vNext status then replay Kinesis or S3 catch-up|separate producer ambiguity, iterator lag, poison, and sink runbooks; reconcile accepted orders to executions, postings, and published effects|minimal outbox/transport plus limited status and audit writes; batching and storage; replay bandwidth|long-lived matcher capacity, shard/key design, lag alarms, archive, reconciliation and incident ownership|hard invariant spans asynchronous consumers or Lambda latency/runtime is assumed suitable for matching|5,000 average and 8,000 peak events/s at 700 B; 13-shard planning example; 900 s outage and 18.75 min ideal drain| Variant | Authoritative state | Consistency/freshness lag and overload policy | Ordering scope | Read-your-writes | Duplicate handling | Rebuild method | Recovery/reconciliation | Variable cost | Operational cost | Poor fit | Quantified workload |
|---|---|---|---|---|---|---|---|---|---|---|---|
| CQRS-lite | accepted order, AP12 ledger/reservation, and risk state | AP07 portfolio p95 under 3 s; at 30 s stop projection-dependent UX and fall back to authority or pending status | per orderId transition and per accountId ledger sequence; no global order | return command receipt with orderId and version; strong base read or wait up to 2 s for projectionVersion at least receipt version | transactional inbox eventId plus conditional next aggregateVersion; duplicate returns stored receipt | PITR export plus outbox/event catch-up into versioned AP07 namespace; control totals then blue-green cutover | repair AP16 outbox and parked gaps; reconcile orders, fills, postings, reservations, and AP07 by batch | DynamoDB transaction and GSI units; Streams/Lambda relay; projection reads/writes; export and replay | projection ownership, alarms, schema/upcaster, rebuild drills, reconciliation and on-call | commands that must decide from AP07 or workload stays inside one store SLO | 5,000 accepted orders/s planning peak; 2,000 AP06 queries/s; one portfolio projection |
| Read-heavy brokerage | order, execution, AP12 ledger/reservation, and risk command stores | portfolio/search/cache p95 under 2 s normal and under 30 s recovery; freeze stale widgets, serve receipt overlay, and shed analytics before authority | per accountId or orderId source version; Kinesis partition key preserves only its documented shard scope; cross-key facts reconcile | receipt overlay and minVersion query token; wait up to 2 s then return pending or read authoritative API | one inbox eventId and sourceVersion condition per projection; stale and duplicate no-op, gaps park | export authoritative domains to S3 manifest; isolated portfolio-vNext, OpenSearch-vNext, and cache warm; catch up and cut over separately | SQS DLQs and retained Kinesis/S3 replay feed isolated rebuild; reconcile counts, money totals, versions, and search coverage | transport fan-out; four projection/index writes; OpenSearch, ElastiCache, S3 and query compute/storage | four-store ownership, schema compatibility, security, lag/on-call, game days and capacity reservations | read/write ratio below modeled threshold or customer action needs strongly current cross-domain state | 1,000 commands/s peak; 50,000 reads/s peak; 4 projections; 2 s normal freshness and 30 s overload budget |
| Write-heavy execution stream | accepted order and reservation/risk state before execution; matcher and authoritative execution/ledger records after execution | status projection p95 under 5 s and hard alarm at 60 s; preserve ingestion by pausing analytics, rate-limit low-priority commands, and drain within retention | instrumentId sequencing lane for matcher and accountId or orderId versions around it; no cross-shard total order | command receipt and authoritative status endpoint; never infer acceptance or fill from stream position | producer eventId plus authoritative expected version; projector inbox; deterministic fill identity and effect receipt | retain authoritative executions/outbox beyond Kinesis window; bulk load compacted vNext status then replay Kinesis or S3 catch-up | separate producer ambiguity, iterator lag, poison, and sink runbooks; reconcile accepted orders to executions, postings, and published effects | minimal outbox/transport plus limited status and audit writes; batching and storage; replay bandwidth | long-lived matcher capacity, shard/key design, lag alarms, archive, reconciliation and incident ownership | hard invariant spans asynchronous consumers or Lambda latency/runtime is assumed suitable for matching | 5,000 average and 8,000 peak events/s at 700 B; 13-shard planning example; 900 s outage and 18.75 min ideal drain |
- Variant
- CQRS-lite
- Authoritative state
- accepted order, AP12 ledger/reservation, and risk state
- Consistency/freshness lag and overload policy
- AP07 portfolio p95 under 3 s; at 30 s stop projection-dependent UX and fall back to authority or pending status
- Ordering scope
- per orderId transition and per accountId ledger sequence; no global order
- Read-your-writes
- return command receipt with orderId and version; strong base read or wait up to 2 s for projectionVersion at least receipt version
- Duplicate handling
- transactional inbox eventId plus conditional next aggregateVersion; duplicate returns stored receipt
- Rebuild method
- PITR export plus outbox/event catch-up into versioned AP07 namespace; control totals then blue-green cutover
- Recovery/reconciliation
- repair AP16 outbox and parked gaps; reconcile orders, fills, postings, reservations, and AP07 by batch
- Variable cost
- DynamoDB transaction and GSI units; Streams/Lambda relay; projection reads/writes; export and replay
- Operational cost
- projection ownership, alarms, schema/upcaster, rebuild drills, reconciliation and on-call
- Poor fit
- commands that must decide from AP07 or workload stays inside one store SLO
- Quantified workload
- 5,000 accepted orders/s planning peak; 2,000 AP06 queries/s; one portfolio projection
- Variant
- Read-heavy brokerage
- Authoritative state
- order, execution, AP12 ledger/reservation, and risk command stores
- Consistency/freshness lag and overload policy
- portfolio/search/cache p95 under 2 s normal and under 30 s recovery; freeze stale widgets, serve receipt overlay, and shed analytics before authority
- Ordering scope
- per accountId or orderId source version; Kinesis partition key preserves only its documented shard scope; cross-key facts reconcile
- Read-your-writes
- receipt overlay and minVersion query token; wait up to 2 s then return pending or read authoritative API
- Duplicate handling
- one inbox eventId and sourceVersion condition per projection; stale and duplicate no-op, gaps park
- Rebuild method
- export authoritative domains to S3 manifest; isolated portfolio-vNext, OpenSearch-vNext, and cache warm; catch up and cut over separately
- Recovery/reconciliation
- SQS DLQs and retained Kinesis/S3 replay feed isolated rebuild; reconcile counts, money totals, versions, and search coverage
- Variable cost
- transport fan-out; four projection/index writes; OpenSearch, ElastiCache, S3 and query compute/storage
- Operational cost
- four-store ownership, schema compatibility, security, lag/on-call, game days and capacity reservations
- Poor fit
- read/write ratio below modeled threshold or customer action needs strongly current cross-domain state
- Quantified workload
- 1,000 commands/s peak; 50,000 reads/s peak; 4 projections; 2 s normal freshness and 30 s overload budget
- Variant
- Write-heavy execution stream
- Authoritative state
- accepted order and reservation/risk state before execution; matcher and authoritative execution/ledger records after execution
- Consistency/freshness lag and overload policy
- status projection p95 under 5 s and hard alarm at 60 s; preserve ingestion by pausing analytics, rate-limit low-priority commands, and drain within retention
- Ordering scope
- instrumentId sequencing lane for matcher and accountId or orderId versions around it; no cross-shard total order
- Read-your-writes
- command receipt and authoritative status endpoint; never infer acceptance or fill from stream position
- Duplicate handling
- producer eventId plus authoritative expected version; projector inbox; deterministic fill identity and effect receipt
- Rebuild method
- retain authoritative executions/outbox beyond Kinesis window; bulk load compacted vNext status then replay Kinesis or S3 catch-up
- Recovery/reconciliation
- separate producer ambiguity, iterator lag, poison, and sink runbooks; reconcile accepted orders to executions, postings, and published effects
- Variable cost
- minimal outbox/transport plus limited status and audit writes; batching and storage; replay bandwidth
- Operational cost
- long-lived matcher capacity, shard/key design, lag alarms, archive, reconciliation and incident ownership
- Poor fit
- hard invariant spans asynchronous consumers or Lambda latency/runtime is assumed suitable for matching
- Quantified workload
- 5,000 average and 8,000 peak events/s at 700 B; 13-shard planning example; 900 s outage and 18.75 min ideal drain
Read-heavy brokerage architecture
Section titled “Read-heavy brokerage architecture”Assume 1,000 commands/s peak, 50,000 reads/s peak, four independently
owned projections, normal freshness p95 below two seconds, and a 30-second
overload budget. The query side denormalizes the portfolio/API view in
DynamoDB, uses OpenSearch for discovery, ElastiCache for disposable hot data,
and S3/Athena for analytics. These stores match different APs and cost shapes;
none authorizes money or risk (C46,C52,C92; A32,A34,A35,A78,A75, retrieved
2026-08-22).
Diagram text source
flowchart LR C[Client] --> A[API Gateway and command Lambda] A --> T[(Order plus AP12 authority and semantic outbox)] T --> S[DynamoDB Streams relay] S --> K[Kinesis retained fan-out] K --> P[Portfolio projector] K --> O[Search projector] K --> H[Analytics projector] P --> D[(DynamoDB portfolio vN)] O --> OS[(OpenSearch index vN)] P --> E[(ElastiCache disposable cache)] H --> B[(S3 then Athena)] C --> Q[Query API with minVersion] Q --> D Q --> OS Q --> E Q -. receipt overlay or authority fallback .-> TInference: write amplification is deliberate: one authoritative commit, one semantic relay path, and four projection/index writes are paid because 50 reads occur per peak write and the query engines avoid repeated joins and scans (C49,C95,C102; A45,A46,A78, retrieved 2026-08-22; F19,F20). Measure actual read work avoided; do not count a cache hit and underlying avoided query twice.
Inference: read-your-writes returns {orderId, acceptedVersion}. The query accepts
minVersion; it either sees a projection at or beyond that version, overlays
the receipt/pending command state, waits at most two seconds, or routes a bounded
strong base-table read. It never claims the whole cross-domain portfolio is a
transactional snapshot. Under overload, freeze stale widgets with an “as of”
time, shed analytics and refresh traffic, and protect command/ledger capacity
(C44,C48,C61; F13,F14).
Inference: residual risks are cross-account skew, OpenSearch mapping failures, stale cache invalidation, four schema migrations, replay load, and a user seeing mutually different “as of” versions. Poor fit: the measured read/write ratio is below break-even, a query must decide a current monetary invariant, or the team cannot rebuild and operate four stores. Track transport failures, per-projection lag, gap age, inbox duplicates, DLQ/redrive outcomes, rebuild progress, and business reconciliation separately; a green service dashboard is not proof of correct financial state (C43,C68).
Write-heavy execution-stream architecture
Section titled “Write-heavy execution-stream architecture”Use the reviewed Kinesis workload (source model 5): 5,000 records/s average, 8,000/s peak,
700 B average payload, 13 provisioned-shard planning result, two full-stream
consumers, a 900-second outage, and 18.75-minute ideal drain at 9,000 commits/s.
Kinesis order and replay stay shard/retention scoped; producer ambiguity,
parallel consumers, poison records, and lag still require stable identity,
versions, isolation, and reconciliation (C41,C72-C77; A46,A96-A99, retrieved
2026-08-22).
Diagram text source
flowchart LR C[Client command] --> G[Acceptance API] G --> R[(Order reservation risk authority and outbox)] R --> L[Outbox relay] L --> K[Kinesis by evidenced key] K --> M[Long-lived matcher and sequencer] M --> X[(Execution and ledger authority plus outbox)] X --> KS[Kinesis status and audit feed] KS --> P[(Limited status projection)] KS --> S3[(S3 durable replay archive)] R --> Q[Authoritative status API] X --> QThe matching loop remains outside the default Lambda/serverless design. Use long-lived ECS/Fargate/EC2 or another evidenced sequencer/runtime for matching; Lambda/serverless surrounds it for command acceptance, workflows, projections, notification, reconciliation, and audit (C49,C53; A44,A58,A59,A60, retrieved 2026-08-22; F09).
Inference: a random Lambda execution simulation, managed scaling label, or stream throughput number does not prove deterministic latency or sequencing fitness (CS12).
Keep synchronous fan-out minimal: acceptance transaction, reservation/risk
decision, and semantic outbox. Batch transport and projection writes only where
their latency budget allows. Partition the matcher by an evidenced sequencing
scope such as instrumentId; preserve per-order/account versions around that
scope, and coordinate/reconcile cross-key monetary effects. One hot aggregate
still serializes; reject, queue fairly, or redesign the aggregate rather than
pretend more shards split it (C34,C44,C75,C87; A95,A105, retrieved 2026-08-22).
Inference: Kinesis is the default buffer in the reviewed Kinesis planning contract (source model 5). Choose Amazon MSK instead only when Kafka protocol/ecosystem, consumer-group, retention, or transaction requirements earn its cluster and operational model—not as a generic “more scalable queue.” For either choice, batching changes request efficiency rather than aggregate authority, and partition/key skew, consumer lag, replay, poison isolation, and sink capacity remain explicit (C29,C42,C44; A14,A30, retrieved 2026-08-22).
Inference: at 60 seconds of status lag, pause analytics, isolate replay, rate-limit lower- priority commands, and preserve authoritative ingestion. Async compaction builds a new status generation from authoritative executions plus retained catch-up; it does not compact away audit/ledger authority. Poor fit: a hard invariant would depend on asynchronous consumers, the sequencing scope is undefined, or Kinesis/S3 retention cannot cover detection plus rebuild plus drain (C44,C68, C77; A36,A98,A99, retrieved 2026-08-22).
Mixed workload: repository CQRS-lite
Section titled “Mixed workload: repository CQRS-lite”The default keeps the reviewed DynamoDB transaction (source model 6): accepted order, durable idempotency result, uniqueness sentinel, and semantic outbox; after commit the named GSIs add ordinary asynchronous writes. AP12 ledger/reservation/risk state is authoritative, while AP07 portfolio is a versioned projection (C83,C90,C91, C95; A100,A101,A104, retrieved 2026-08-22).
The named worked route is not “one write”: four transaction-targeted base
items consume 12 modeled units and three asynchronously maintained GSI entries
add 3, for 15. AP16's complete create/claim/complete/retry/reschedule lifecycle
models 40,440 base write units/s plus 10,240 sparse READY-index units/s,
or 50,680 total. A CQRS decision must preserve those transaction/index,
insert/delete/key-move, retry, and rebuild writes rather than count only the
serving projection (C95; A100,A104,A105, retrieved 2026-08-22).
Diagram text source
flowchart LR C[Client] --> A[Order command Lambda] A --> D[(DynamoDB order AP12 authority plus AP16 outbox)] D --> S[DynamoDB Streams relay] S --> B[EventBridge routing] B --> Q[SQS owned work] Q --> P[Idempotent AP07 projector] P --> V[(Portfolio vN rebuildable)] C --> R[Read API] R --> V R -. minVersion fallback .-> D D --> J[Reconciliation and repair] V --> JAt the planning peak, this chapter reuses the DynamoDB model 6 inputs of 5,000 accepted orders/s,
2,000 AP06 queries/s, and one AP07 projection. Normal AP07 p95 freshness is
under three seconds; at 30 seconds the product disables projection-dependent
actions, displays pending/as-of state, and falls back to authoritative reads
where bounded.
Inference: add a specialized store only after query traces show the current model misses its SLO, the break-even remains positive under sensitivity, and an owner proves rebuild/reconciliation (C48,C97,C102).
Residual risk remains around cross-item Stream interleaving, EventBridge target
delivery, projector retry/gaps, and restore beyond 24 hours. The semantic
outbox represents one business event; AP09 inbox plus source version protects
AP07; a PITR export and catch-up build portfolio-vNext; control totals and a
blue/green pointer switch complete recovery (C84,C88,C96,C99,C100; A102,A106,
retrieved 2026-08-22).
Outbox and CDC: the atomicity gap each variant closes
Section titled “Outbox and CDC: the atomicity gap each variant closes”All three variants depend on a trustworthy handoff from committed state to consumers. This is the common mechanism beneath their different service diagrams. Compare raw change data capture (CDC) with an explicit semantic outbox, and keep the local transaction's promise separate from relay acknowledgements and downstream effects.
| Variant | Atomicity gap closed | What remains |
|---|---|---|
| DynamoDB semantic outbox item + Streams relay | TransactWriteItems commits authoritative items and one business publication-intent item together inside its documented account/Region transaction boundary. The relay can later publish that envelope. | Transaction item changes can interleave in Streams; Lambda can repeat; publication can be ambiguous; consumer effects need event ID, inbox/version guards, replay and reconciliation (C83,C84,C99; A20,A21,A101,A102,A115, retrieved 2026-08-22; F04,F11). |
| Raw DynamoDB Streams CDC | It removes an application poll between a committed item change and observation of that change. It does not create one semantic envelope for a multi-item transaction or preserve beyond 24 hours. | Consumers infer meaning from storage rows, transaction changes can interleave, duplicates occur, and long rebuild needs another source. Use only when row-level CDC is the contract (C84,C96,C99; A21,A102,A106, retrieved 2026-08-22). |
| Relational outbox polling/CDC | One relational transaction commits business rows and the outbox row, closing local state versus publication-intent dual write. Polling or transaction-log CDC relays committed rows. | Poll/CDC checkpoints, ambiguous broker acknowledgements, repeat publication, cleanup retention, schema evolution, consumer idempotency, and reconciliation remain (C38,C40,C99; A115, retrieved 2026-08-22; F04,F11). |
| Direct database/broker dual write | No atomicity gap is closed: database-first can commit without publish; broker-first can publish without state; timeout makes either result ambiguous. Direct database/broker dual write is unsafe as the default. | A repair log discovered from authority can reduce loss, but it has become an outbox/reconciliation design. Never call two SDK successes a transaction (C38,C99; F04). |
- Variant
- DynamoDB semantic outbox item + Streams relay
- Atomicity gap closed
TransactWriteItemscommits authoritative items and one business publication-intent item together inside its documented account/Region transaction boundary. The relay can later publish that envelope.- What remains
- Transaction item changes can interleave in Streams; Lambda can repeat; publication can be ambiguous; consumer effects need event ID, inbox/version guards, replay and reconciliation (C83,C84,C99; A20,A21,A101,A102,A115, retrieved 2026-08-22; F04,F11).
- Variant
- Raw DynamoDB Streams CDC
- Atomicity gap closed
- It removes an application poll between a committed item change and observation of that change. It does not create one semantic envelope for a multi-item transaction or preserve beyond 24 hours.
- What remains
- Consumers infer meaning from storage rows, transaction changes can interleave, duplicates occur, and long rebuild needs another source. Use only when row-level CDC is the contract (C84,C96,C99; A21,A102,A106, retrieved 2026-08-22).
- Variant
- Relational outbox polling/CDC
- Atomicity gap closed
- One relational transaction commits business rows and the outbox row, closing local state versus publication-intent dual write. Polling or transaction-log CDC relays committed rows.
- What remains
- Poll/CDC checkpoints, ambiguous broker acknowledgements, repeat publication, cleanup retention, schema evolution, consumer idempotency, and reconciliation remain (C38,C40,C99; A115, retrieved 2026-08-22; F04,F11).
- Variant
- Direct database/broker dual write
- Atomicity gap closed
- No atomicity gap is closed: database-first can commit without publish; broker-first can publish without state; timeout makes either result ambiguous. Direct database/broker dual write is unsafe as the default.
- What remains
- A repair log discovered from authority can reduce loss, but it has become an outbox/reconciliation design. Never call two SDK successes a transaction (C38,C99; F04).
Inference: an outbox is not a forever event store. Retain unpublished/repair evidence past the maximum relay outage, record attempts and broker receipts, delete/archive only under a proven lifecycle, and reconcile “authoritative event expected” to “target business effect observed.” It closes one local atomicity gap; duplicates, replay, idempotency, and reconciliation remain (C38,C43,C99; F04,F11).
Outbox and CDC operational contract
Section titled “Outbox and CDC operational contract”Problem / why. A command can commit state and crash before publishing, or publish and then roll back/timeout. One semantic outbox item committed with the state makes publication intent durably discoverable without a broker/database distributed transaction (C38,C99; F04; A115, retrieved 2026-08-22).
Guarantee scope and stopping boundary. The guarantee stops at the local database transaction containing authoritative state and outbox intent. Relay read, broker acknowledgement, target delivery, consumer commit, and external effect remain retryable or ambiguous and require stable identity, inbox/version guards, receipts, and reconciliation (C38,C40,C84,C99; F04,F11).
Concrete workload. Reuse the reviewed 5,000 accepted orders/s case: four transaction-targeted items consume 12 modeled base units, three asynchronous GSI entries add 3, and the balanced AP16 relay lifecycle totals 50,680 modeled write units/s including READY-index movement. The numbers are planning inputs, not a universal outbox price (C95; A100,A104,A105, retrieved 2026-08-22).
Fit and poor fit. Fit is a local commit whose publication must survive a crash. Poor fit is row-level analytics CDC that needs no semantic envelope, an already-authoritative append log with a proven publisher, or a cross-service invariant falsely being hidden behind an outbox.
Failure and recovery. Persist relay lease, attempt, broker response, and
next-due state; recover expired IN_FLIGHT items to READY; retry ambiguous
publication under the same event ID; park poison; replay from retained outbox/
export; reconcile expected events to business effects before cleanup (C43,C90,
C99; F04,F11).
Variable cost. Count extra transactional item bytes/units, sparse READY index insert/delete/move work, Streams reads, Lambda/relay invocations, broker requests/bytes, retention, retry/replay, DLQ storage, and reconciliation.
Fixed ownership. The producing domain owns the semantic schema and outbox lifecycle; the relay owner owns leases/checkpoints/broker ambiguity; each consumer owns inbox/effects; operations owns alarms, repair, retention, and a reconciliation runbook.
Metric, threshold, response. Measure oldest READY age, READY/IN_FLIGHT counts, expired leases, publish failures/ambiguities, duplicate inbox hits, DLQ age, and expected-versus-observed effect breaks. At 30 seconds oldest READY age or any missing financial effect, page, protect relay capacity, stop cleanup, repair leases/gaps, replay idempotently, and reconcile (C43,C68,C99; A36, retrieved 2026-08-22).
Anti-pattern to reject. Publishing directly after a database commit and calling the pair atomic, or treating a raw change row as a stable business event.
Follow-up questions. Which local atomicity gap is closed? Where are relay checkpoints, event identity, repair retention, consumer idempotency, and proof that committed intents reached every required sink?
Two-minute defense. “The outbox closes only state-versus-publication-intent atomicity in one database transaction. The relay may publish twice and the consumer may run twice, so event identity, inbox/version checks, effect receipts, retained repair state, and reconciliation—not broker wording—prove the outcome.”
Idempotent projections and controlled rebuilds
Section titled “Idempotent projections and controlled rebuilds”For each projection event, carry eventId, aggregateId, aggregateVersion,
eventType, schemaVersion, occurred/recorded time, causation/correlation, and
producer. In one target transaction or conditional boundary:
- create an inbox receipt keyed by stable
eventId; - require
incomingVersion = currentVersion + 1for the aggregate; - apply the projection mutation and store the new source/projection version;
- return the stored effect receipt for a duplicate;
- advance a source checkpoint only after the target commit.
Inference: stale versions no-op with evidence; the next version applies; a future version parks in a gap queue and alarms by age/key. Deduplication alone does not order events, and a version check alone does not deduplicate different event IDs (C40,C41,C93,C100; F01,F02,F11).
Inference: a controlled rebuild is blue/green, not in-place (C88,C96,C100; A106, retrieved 2026-08-22):
- declare target projection/schema version and a source watermark;
- export authoritative state/events with a manifest and control totals;
- load an isolated
vNextnamespace with notifications/payments disabled; - catch up from the watermark using separate capacity, checkpoints, versions, and rate limits so live traffic retains headroom;
- compare counts, aggregate versions, cash/security totals, missing IDs, and sampled query equivalence;
- switch readers atomically or by bounded cohort, retain
vOldfor rollback, then archive evidence.
Rebuild completion is not merely “iterator age zero.” Reconciliation proves the served result against authority. Backfill identity must be stable across restarts, and backfill traffic must be isolated from live projections.
Projection and rebuild operational contract
Section titled “Projection and rebuild operational contract”Problem / why. A projection precomputes one expensive read shape and isolates query demand, but it deliberately duplicates authoritative facts and can be stale, duplicated, out of order, or structurally incompatible after a schema change (C41,C46,C100; F01,F02).
Guarantee scope and stopping boundary. Inbox identity plus the conditional next source version protects one named projection mutation. It does not make cross-aggregate views atomic, prove checkpointed external effects, or permit a portfolio/search/cache row to authorize balance, reservation, risk, order, or posting decisions (C40,C41,C46,C100; F01,F11).
Concrete workload. CQRS-lite assumes 5,000 accepted source changes/s, one portfolio projection, 2,000 AP06 queries/s, normal p95 freshness below three seconds, and degradation at 30 seconds. The read-heavy case assumes 1,000 commands/s, 50,000 reads/s, four projections, two-second normal and 30-second recovery freshness.
Fit and poor fit. Fit is a stable access pattern whose avoided read work
pays write/rebuild/on-call burden and tolerates its declared as of time. Poor
fit is a hard invariant, ad hoc query churn, no complete retained source, or no
capacity/owner for an isolated rebuild.
Failure and recovery. Duplicate/stale versions no-op with receipts; future
versions park and alarm. Export authority at a watermark, load vNext with
external effects suppressed, catch up on isolated capacity, compare versions/
counts/money totals, canary readers, and blue/green cut over with vOld
rollback (C88,C96,C100; A102,A106, retrieved 2026-08-22).
Variable cost. Count one inbox/conditional projection mutation per event, target index/storage/cache/search work, source reads, gap/DLQ retention, full backfill and catch-up compute/I/O, validation queries, and temporary blue/green double capacity.
Fixed ownership. A projection owner maintains schemas/upcasters, source-to- target version rules, checkpoints, gap queues, rebuild manifests, cutover/ rollback tooling, control totals, SLOs, capacity isolation, and on-call.
Metric, threshold, response. Track p95/max source age, gap oldest age, duplicate/stale counts, checkpoint minus committed target version, rebuild ETA, control-total breaks, and query fallback. At 30 seconds age or any version/money break, freeze affected reads/actions, isolate the key, backfill/replay, compare authority, and cut over only after zero unexplained breaks (C43,C48,C68,C100; A36, retrieved 2026-08-22).
Anti-pattern to reject. Rebuilding a live projection in place, overwriting newer versions, or using a strong read of a derived view as monetary authority.
Follow-up questions. What source covers a full rebuild, how are gaps parked, what watermark makes catch-up safe, and which totals authorize blue/green cutover?
Two-minute defense. “A projection is an optimization with a versioned
freshness contract, never authority. I atomically combine inbox identity,
expected next version, mutation, and receipt; park gaps; and rebuild into
vNext from a complete source with isolated catch-up, control totals, canary
cutover, and rollback.”
Event sourcing as an independent command model
Section titled “Event sourcing as an independent command model”For aggregate A, load the latest compatible snapshot (A, snapshotVersion, state, reducerVersion), read authoritative events after that version, and fold
them deterministically. A command validated against version n emits events
and performs an optimistic expected-version append requiring the stream tail
still equal n; conflict means reload and re-evaluate, not blind retry
(C36,C98; F07,F36; A114, retrieved 2026-08-22).
Snapshots are derived acceleration. Snapshot invalidation is mandatory when the event position, reducer, or schema is incompatible. Store the event position and reducer/schema version; invalidate and rebuild a snapshot when an upcaster/reducer change makes it incompatible. Never delete the underlying authoritative sequence merely because a snapshot exists. Use immutable historical payloads plus versioned upcasters into the current in-memory domain shape; for a semantic break, add a new event type and explicit migration rather than reinterpret history (C59,C98; F01,F07).
Inference: temporal queries replay to a recorded position/business time under the event contract; they must distinguish recorded order from effective business time. Run what-if simulations against copied streams/namespaces and label them non- authoritative. Replay isolation uses separate consumers/capacity/checkpoints, deterministic reducers, and external-effect suppression. Historical replay must not resend a transfer, email, market order, or compensation; integrations use an explicit live-mode gate, stable effect ledger/receipt, and reconciliation (C59,C98,C100; F01,F07).
Inference: Kinesis retention alone is not an event store. Kinesis can be one component only if authoritative append concurrency, complete retention, aggregate reads, immutable schema, backup/restore, temporal access, and reconciliation are deliberately supplied (C84,C98; A102,A114, retrieved 2026-08-22).
Inference: DynamoDB Streams alone is not an event store: it is 24-hour CDC over table mutations, and those mutations need not be the authoritative domain-event sequence (C84,C98; A102,A114, retrieved 2026-08-22).
Event-store recovery restores immutable events and metadata to a new namespace, verifies hashes/counts/per-aggregate contiguous versions, reconstructs sampled and then all aggregates, rebuilds projections with effects suppressed, catches up live appends behind a write fence/watermark, reconciles, and cuts over. An event archive is not automatically a financial ledger: ledger postings still need balancing, exact money, reversals/corrections, and reconciliation (C47,C91,C98; F08,F17).
Inference: event sourcing is a poor fit when the team cannot freeze historical meaning, replay deterministically, recover the store, or prevent duplicate external effects; when aggregate histories are unbounded without a safe snapshot policy; or when current-state CRUD plus ledger/outbox already meets audit and temporal requirements (C98; F01,F07).
Event-sourcing operational contract
Section titled “Event-sourcing operational contract”Problem / why. Event sourcing earns its cost when the authoritative domain must preserve every accepted transition, explain how state arose, reconstruct a point in time, or derive new views from immutable history—not merely because a service already publishes integration events (C36,C98; F01,F07).
Guarantee scope and stopping boundary. Expected-version append serializes one aggregate stream and its event sequence is authoritative for that aggregate. It does not provide cross-aggregate ACID, broker delivery, one-time consumer effects, accounting semantics, or indefinite correctness from a snapshot (C34,C47,C98; F01,F07,F08).
Concrete workload. Evaluate at 1,000 commands/s peak, median 40 events per aggregate, p99 5,000 events, a snapshot evaluation every 500 events, seven-year authoritative retention, and a 60-minute projection-rebuild RTO. These are explicit design-test assumptions, not claims about the repository or an AWS service.
Fit and poor fit. Fit is load-tested reconstruction plus a governed historical/temporal requirement. Poor fit is current-state CRUD with sufficient ledger/audit, mutable event meaning, uncontrolled side effects during replay, unbounded aggregates without compatible snapshots, or no event-store recovery owner (C98; F01,F07).
Failure and recovery. On append conflict, reload and re-evaluate the command; on incompatible snapshot, discard/rebuild it; on corrupt/gapped stream, fence writes, restore events/metadata to a new namespace, verify per-aggregate contiguous versions/hashes, rebuild with effects suppressed, catch up, reconcile, and cut over (C59,C98,C100; F01,F07).
Variable cost. Count immutable append bytes/requests and indexes, snapshot writes/storage, aggregate reads/reconstruction CPU, long retention/archive, upcasting, temporal queries, projection fan-out, replay bandwidth, temporary rebuild capacity, and reconciliation.
Fixed ownership. The domain team owns immutable event meaning, reducers, expected-version behavior, snapshot policy, upcasters/migrations, temporal semantics, effect suppression, store backup/restore, rebuild tooling, audit access, and incident response.
Metric, threshold, response. Measure append conflict ratio, append p99, events/reconstruction time per aggregate, snapshot hit/invalidation, version gaps, upcast failures, replay rate/ETA, rebuild RTO, and reconciliation breaks. At more than 2% conflicts for five minutes, any event gap/upcast failure, or projected 60-minute RTO miss, throttle/fence the affected scope, diagnose writers/schema, restore/replay in isolation, and reconcile before cutover.
Anti-pattern to reject. Calling a retained transport an event store, mutating old event meaning, or replaying history through live payment/notification effects.
Follow-up questions. What is the append concurrency contract? How are old schemas upcast, snapshots invalidated, effects suppressed, and the event store restored within RTO?
Two-minute defense. “Event sourcing changes authority: the per-aggregate event sequence, appended at an expected version, rebuilds state. I adopt it only for an immutable temporal/audit need, with compatible snapshots, upcasters, isolated replay and effects suppression, complete store recovery, metrics, and reconciliation; Kinesis or Streams alone supplies none of that contract.”
Sagas: choreography versus orchestration
Section titled “Sagas: choreography versus orchestration”A replayable aggregate and a rebuildable projection still do not create one transaction across a bank, venue, and ledger. Sagas address that cross-owner journey. For the example you carry through this section, mark each locally committed step, the first irreversible pivot, and the evidence needed to decide whether a timed-out external step happened.
A saga coordinates local transactions; it does not create cross-service ACID isolation. Compensation is a forward business action, not rollback. A released reservation may fail or conflict; a submitted market order may require cancel, amend, offsetting trade, or human escalation rather than erasure (C39,C101; F05,F10; A116, retrieved 2026-08-22).
| Dimension | Choreography with EventBridge/SQS | Orchestration with Step Functions Standard |
|---|---|---|
| Process state | Emerges from participant events plus each service's durable state/inbox/outbox. | Explicit workflow state/history, branches, retry/catch, timers, and callbacks. |
| Coupling | Producers know event contracts, not a central step sequence; global dependency graph becomes harder to see. | Orchestrator knows sequence and commands; participants need not know one another, but workflow definition becomes a coupling/deployment boundary. |
| Good fit | Few participants, simple reactions, high autonomy, no long opaque chain. | Multi-step order/funding/compliance process, durable timeout, human wait, and explicit audit path. |
| Failure controls | Per-subscription SQS ownership, visibility/DLQ, event ID, timeout events, process-status projection, reconciliation. | Idempotent task tokens/commands, effect receipt before transition, retry/catch, wait/callback, timeout, compensation branch, reconciliation. |
| Residual boundary | EventBridge archive/replay is not original order and SQS/Lambda can repeat. | Standard workflow execution semantics do not make downstream effects exactly once. |
- Dimension
- Process state
- Choreography with EventBridge/SQS
- Emerges from participant events plus each service's durable state/inbox/outbox.
- Orchestration with Step Functions Standard
- Explicit workflow state/history, branches, retry/catch, timers, and callbacks.
- Dimension
- Coupling
- Choreography with EventBridge/SQS
- Producers know event contracts, not a central step sequence; global dependency graph becomes harder to see.
- Orchestration with Step Functions Standard
- Orchestrator knows sequence and commands; participants need not know one another, but workflow definition becomes a coupling/deployment boundary.
- Dimension
- Good fit
- Choreography with EventBridge/SQS
- Few participants, simple reactions, high autonomy, no long opaque chain.
- Orchestration with Step Functions Standard
- Multi-step order/funding/compliance process, durable timeout, human wait, and explicit audit path.
- Dimension
- Failure controls
- Choreography with EventBridge/SQS
- Per-subscription SQS ownership, visibility/DLQ, event ID, timeout events, process-status projection, reconciliation.
- Orchestration with Step Functions Standard
- Idempotent task tokens/commands, effect receipt before transition, retry/catch, wait/callback, timeout, compensation branch, reconciliation.
- Dimension
- Residual boundary
- Choreography with EventBridge/SQS
- EventBridge archive/replay is not original order and SQS/Lambda can repeat.
- Orchestration with Step Functions Standard
- Standard workflow execution semantics do not make downstream effects exactly once.
Documented: Step Functions Standard follows exactly-once workflow execution:
tasks and states are not run more than once unless ASL Retry is configured.
Boundary: Task invocation, provider completion, and external effects can be
ambiguous or non-atomic relative to workflow history.
Design response: Use stable identity, idempotency, effect receipts, provider
status lookup by business key, compensation, and reconciliation.
Asynchronous Express follows at-least-once workflow execution; Synchronous
Express follows at-most-once workflow execution (C25,C65; A25, retrieved
2026-08-22). Standard is the long-running, durable/auditable type and supports
job-run/callback integrations that Express does not (A25,A26,A87, retrieved
2026-08-22). For example, a task can time out after an external provider
commits.
Workflow labels do not make arbitrary payments, postings, orders, or
notifications one-time effects. This does not make downstream effects exactly once.
A semantic lock exposes the intermediate business state—FUNDS_RESERVED,
ORDER_PENDING_COMPLIANCE, CANCEL_REQUESTED—and constrains concurrent actions
without pretending database isolation spans the saga. Every lock has owner,
expiry/timeout policy, allowed transitions, customer visibility, and repair.
The pivot is the step after which normal backward compensation is impossible or
undesirable; after broker submission or final posting, prefer forward recovery,
explicit reversal/correction, or human resolution. Store causation, attempts,
receipts, actor, timestamps, and compensation result in durable audit history.
Inference: use EventBridge/SQS choreography for the repository's simple post-acceptance projection/notification reactions, but Step Functions Standard for explicit multi-step funding/compliance workflows with human wait or deadline state (C60,C62,C63,C65,C101; A84,A85,A116, retrieved 2026-08-22). Neither path replaces service-owned inbox/outbox, idempotent tasks, effect receipts, timeouts, or reconciliation.
Saga operational contract
Section titled “Saga operational contract”Problem / why. A funding/compliance/order journey spans local transaction owners and may wait on providers or humans; a saga makes forward progress, timeouts, and compensating business actions explicit without pretending one distributed ACID transaction exists (C39,C101; F05,F10).
Guarantee scope and stopping boundary. Choreography routes participant facts/commands; orchestration durably records workflow decisions. Neither atomically commits a participant database/provider effect with workflow state, and compensation cannot erase an irreversible pivot (C39,C65,C101; F05,F10).
Concrete workload. Evaluate 200 workflows/s peak, six idempotent local tasks, a five-minute automated-process deadline, human approval waits up to 48 hours with a 24-hour escalation, and 30-day business-receipt retention. These are workflow design assumptions, not current service quotas.
Fit and poor fit. Fit is a long-running cross-owner process with explicit timeouts/receipts and compensable or forward-recoverable steps. Poor fit is one local aggregate transaction, a hard invariant needing synchronous reservation, an undefined compensation, or a high-frequency matching loop.
Failure and recovery. Retry only classified idempotent tasks; after an ambiguous timeout query provider status by business key/receipt; expire semantic locks deliberately; before pivot compensate completed steps where valid; after pivot prefer forward completion, correction/reversal, or human resolution; reconcile process state to participant effects (C39,C45,C101; F05,F10,F11).
Variable cost. Count EventBridge/SQS messages and retries/DLQ storage for choreography, or Standard state transitions/wait history/callbacks for orchestration, plus task compute, receipt/status calls, compensation, audit, and reconciliation (C49,C65; A47,A48,A49, retrieved 2026-08-22).
Fixed ownership. Choreography needs a process owner despite distributed code; orchestration needs a state-machine owner. Both own schemas, task idempotency, semantic locks, pivot/compensation catalog, timeout/human queues, receipts, audit retention, runbooks, reconciliation, security, and on-call.
Metric, threshold, response. Measure workflow age by state, task retry/ timeout/ambiguity, semantic-lock age, compensation rate/failure, callback/human wait age, DLQ age, and receipt mismatches. At five minutes stuck automated age, 24 hours human wait, any failed compensation, or missing monetary receipt, pause new dependent work, query effects, escalate owner/human, compensate or forward-recover, and reconcile (C43,C48,C68,C101; A36, retrieved 2026-08-22).
Anti-pattern to reject. Treating compensation as database rollback or using workflow execution wording as a promise about one-time provider effects.
Follow-up questions. Where is the pivot, which locks can expire, how are ambiguous effects looked up, and who resolves failed compensation or human waits?
Two-minute defense. “I use choreography for a few observable reactions and Standard orchestration when durable process state, timers, or humans matter. Every task carries stable identity and an effect receipt; ambiguous completion is looked up, compensation is a new business action, pivots move recovery forward, and reconciliation—not workflow wording—proves the financial result.”
Hard financial invariants stay authoritative
Section titled “Hard financial invariants stay authoritative”| Decision | Must read/write | Why projection is insufficient |
|---|---|---|
| Available balance and reservation | AP12 authoritative cash/security balance, pending reservations, exact currency/scale, and version in one governed transaction/aggregate. | Portfolio/cache lag can approve double spend; strong-read projection still has derived provenance. |
| Risk limit | Current authoritative exposure plus accepted reservation under the declared risk consistency boundary. | Async analytics/search is suitable for surveillance, not pre-trade authorization. |
| Order transition | Authoritative order state machine with expected version and stable request/event identity. | Arrival order, FIFO, or a status view cannot validate legal transition. |
| Execution and ledger posting | Deterministic fill identity; authoritative execution transition; balanced append-oriented postings and receipts. | Event bus/archive and mutable portfolio rows do not provide accounting semantics. |
- Decision
- Available balance and reservation
- Must read/write
- AP12 authoritative cash/security balance, pending reservations, exact currency/scale, and version in one governed transaction/aggregate.
- Why projection is insufficient
- Portfolio/cache lag can approve double spend; strong-read projection still has derived provenance.
- Decision
- Risk limit
- Must read/write
- Current authoritative exposure plus accepted reservation under the declared risk consistency boundary.
- Why projection is insufficient
- Async analytics/search is suitable for surveillance, not pre-trade authorization.
- Decision
- Order transition
- Must read/write
- Authoritative order state machine with expected version and stable request/event identity.
- Why projection is insufficient
- Arrival order, FIFO, or a status view cannot validate legal transition.
- Decision
- Execution and ledger posting
- Must read/write
- Deterministic fill identity; authoritative execution transition; balanced append-oriented postings and receipts.
- Why projection is insufficient
- Event bus/archive and mutable portfolio rows do not provide accounting semantics.
Inference: if one hard invariant cannot fit one aggregate/transaction, name the coordination boundary, reservation/semantic lock, timeout, pivot, compensation limits, and reconciliation. Do not hide it behind “eventual consistency” (C34,C39,C46,C47,C91,C101; F08,F10,F17).
CQRS break-even model
Section titled “CQRS break-even model”First establish that the projection meets authority, freshness, and recovery requirements. Then test whether its read benefit pays for its additional work and ownership. The dimensionless CU model below teaches the relationship; the later dated USD model uses different inputs and can reach a different economic conclusion. Keep their units and assumptions separate.
The canonical inputs and arithmetic live in
the shared cost model. All
terms use the same modeled CU; CU is not currency and no mutable AWS price is
introduced here. W is million writes/month, R million reads/month, a is
CU avoided per million reads, v is transport + projection/index write +
storage + replay/rebuild + reconciliation CU per million writes, and F is
fixed operational CU/month (C49,C102; F19,F20).
The next table is this chapter's only reader-visible CQRS-model derivation surface. The verifier derives every row from the cost model's canonical input block and compares both documents.
| Kind | Key | Visible value or equation | Unit |
|---|---|---|---|
| INPUT | writes_million | 10 | million writes/month |
| INPUT | reads_million | 85 | million reads/month |
| INPUT | read_work_avoided_cu_per_million | 1.80 | CU/million reads |
| INPUT | transport_cu_per_million_writes | 0.20 | CU/million writes |
| INPUT | projection_write_cu_per_million_writes | 0.50 | CU/million writes |
| INPUT | storage_cu_per_million_writes | 0.10 | CU/million writes |
| INPUT | replay_rebuild_cu_per_million_writes | 0.08 | CU/million writes |
| INPUT | reconciliation_cu_per_million_writes | 0.12 | CU/million writes |
| INPUT | fixed_operational_cu_per_month | 18 | CU/month |
| INPUT | low_volume_writes_million | 1 | million writes/month |
| INPUT | high_volume_writes_million | 100 | million writes/month |
| INPUT | half_read_benefit_factor | 0.50 | ratio |
| INPUT | double_projection_write_factor | 2 | ratio |
| INPUT | double_storage_factor | 2 | ratio |
| FORMULA | monthly_net | R * a - W * v - F | CU/month |
| FORMULA | break_even_ratio | R/W = (v + F/W) / a | reads/write |
| DERIVATION | variable_cost_per_million_writes | 0.20 + 0.50 + 0.10 + 0.08 + 0.12 = 1.00 | CU/million writes |
| DERIVATION | read_work_avoided | 85 * 1.80 = 153.00 | CU/month |
| DERIVATION | variable_projection_cost | 10 * 1.00 = 10.00 | CU/month |
| DERIVATION | fixed_operational_cost | 18.00 | CU/month |
| DERIVATION | net_benefit | 153.00 - 10.00 - 18.00 = 125.00 | CU/month |
| DERIVATION | break_even | (1.00 + 18.00 / 10) / 1.80 = 1.56 | reads/write |
| DERIVATION | low_volume_sensitivity | (1.00 + 18.00 / 1) / 1.80 = 10.56 | reads/write |
| DERIVATION | high_volume_sensitivity | (1.00 + 18.00 / 100) / 1.80 = 0.66 | reads/write |
| DERIVATION | half_read_benefit_sensitivity | (1.00 + 18.00 / 10) / (1.80 * 0.50) = 3.11 | reads/write |
| DERIVATION | double_projection_storage_sensitivity | 0.20 + 0.50 * 2 + 0.10 * 2 + 0.08 + 0.12 = 1.60; (1.60 + 18.00 / 10) / 1.80 = 1.89 | reads/write |
- Kind
- INPUT
- Key
- writes_million
- Visible value or equation
- 10
- Unit
- million writes/month
- Kind
- INPUT
- Key
- reads_million
- Visible value or equation
- 85
- Unit
- million reads/month
- Kind
- INPUT
- Key
- read_work_avoided_cu_per_million
- Visible value or equation
- 1.80
- Unit
- CU/million reads
- Kind
- INPUT
- Key
- transport_cu_per_million_writes
- Visible value or equation
- 0.20
- Unit
- CU/million writes
- Kind
- INPUT
- Key
- projection_write_cu_per_million_writes
- Visible value or equation
- 0.50
- Unit
- CU/million writes
- Kind
- INPUT
- Key
- storage_cu_per_million_writes
- Visible value or equation
- 0.10
- Unit
- CU/million writes
- Kind
- INPUT
- Key
- replay_rebuild_cu_per_million_writes
- Visible value or equation
- 0.08
- Unit
- CU/million writes
- Kind
- INPUT
- Key
- reconciliation_cu_per_million_writes
- Visible value or equation
- 0.12
- Unit
- CU/million writes
- Kind
- INPUT
- Key
- fixed_operational_cu_per_month
- Visible value or equation
- 18
- Unit
- CU/month
- Kind
- INPUT
- Key
- low_volume_writes_million
- Visible value or equation
- 1
- Unit
- million writes/month
- Kind
- INPUT
- Key
- high_volume_writes_million
- Visible value or equation
- 100
- Unit
- million writes/month
- Kind
- INPUT
- Key
- half_read_benefit_factor
- Visible value or equation
- 0.50
- Unit
- ratio
- Kind
- INPUT
- Key
- double_projection_write_factor
- Visible value or equation
- 2
- Unit
- ratio
- Kind
- INPUT
- Key
- double_storage_factor
- Visible value or equation
- 2
- Unit
- ratio
- Kind
- FORMULA
- Key
- monthly_net
- Visible value or equation
- R * a - W * v - F
- Unit
- CU/month
- Kind
- FORMULA
- Key
- break_even_ratio
- Visible value or equation
- R/W = (v + F/W) / a
- Unit
- reads/write
- Kind
- DERIVATION
- Key
- variable_cost_per_million_writes
- Visible value or equation
- 0.20 + 0.50 + 0.10 + 0.08 + 0.12 = 1.00
- Unit
- CU/million writes
- Kind
- DERIVATION
- Key
- read_work_avoided
- Visible value or equation
- 85 * 1.80 = 153.00
- Unit
- CU/month
- Kind
- DERIVATION
- Key
- variable_projection_cost
- Visible value or equation
- 10 * 1.00 = 10.00
- Unit
- CU/month
- Kind
- DERIVATION
- Key
- fixed_operational_cost
- Visible value or equation
- 18.00
- Unit
- CU/month
- Kind
- DERIVATION
- Key
- net_benefit
- Visible value or equation
- 153.00 - 10.00 - 18.00 = 125.00
- Unit
- CU/month
- Kind
- DERIVATION
- Key
- break_even
- Visible value or equation
- (1.00 + 18.00 / 10) / 1.80 = 1.56
- Unit
- reads/write
- Kind
- DERIVATION
- Key
- low_volume_sensitivity
- Visible value or equation
- (1.00 + 18.00 / 1) / 1.80 = 10.56
- Unit
- reads/write
- Kind
- DERIVATION
- Key
- high_volume_sensitivity
- Visible value or equation
- (1.00 + 18.00 / 100) / 1.80 = 0.66
- Unit
- reads/write
- Kind
- DERIVATION
- Key
- half_read_benefit_sensitivity
- Visible value or equation
- (1.00 + 18.00 / 10) / (1.80 * 0.50) = 3.11
- Unit
- reads/write
- Kind
- DERIVATION
- Key
- double_projection_storage_sensitivity
- Visible value or equation
- 0.20 + 0.50 * 2 + 0.10 * 2 + 0.08 + 0.12 = 1.60; (1.60 + 18.00 / 10) / 1.80 = 1.89
- Unit
- reads/write
| Metric | Value |
|---|---|
| read_work_avoided_cu_month | 153.0 |
| variable_projection_cu_month | 10.0 |
| fixed_operational_cu_month | 18.0 |
| net_benefit_cu_month | 125.0 |
| break_even_reads_per_write | 1.56 |
| low_volume_reads_per_write | 10.56 |
| high_volume_reads_per_write | 0.66 |
| half_benefit_reads_per_write | 3.11 |
| double_projection_reads_per_write | 1.89 |
- Metric
- read_work_avoided_cu_month
- Value
- 153.0
- Metric
- variable_projection_cu_month
- Value
- 10.0
- Metric
- fixed_operational_cu_month
- Value
- 18.0
- Metric
- net_benefit_cu_month
- Value
- 125.0
- Metric
- break_even_reads_per_write
- Value
- 1.56
- Metric
- low_volume_reads_per_write
- Value
- 10.56
- Metric
- high_volume_reads_per_write
- Value
- 0.66
- Metric
- half_benefit_reads_per_write
- Value
- 3.11
- Metric
- double_projection_reads_per_write
- Value
- 1.89
Inference: the validated sensitivity rows show why even a positive modeled
net benefit does not override an unachievable freshness/recovery contract or a hard
authority violation. The CU curve is an architectural sensitivity, not an AWS
invoice. The dated cost model
puts explicit eu-west-1 prices and fixed operations on a different scale:
The dated cost model (source model 10) puts 50 reads/write below its 75.462912 reads/write cost break-even and at -$887.203238/month.
At that dated workload, the projection is bought for a measured 95 ms p99 read-latency gain, 2-second freshness, and command/read isolation—not for cost savings.
Reprice and remeasure before deciding (A44-A49,A71,A73-A75,A78,A113,A123,A125,
retrieved 2026-08-22; C48,C102,C113; F19,F20).
Repository application: CS01–CS12
Section titled “Repository application: CS01–CS12”The canonical evidence remains in the repository evidence map. This table applies the patterns without broadening the observations. Authority, ledger, amplification, publication, and saga controls route through C46,C91,C95,C99,C101 and the established accounting/outbox literature F08,F11,F17; CS01–CS12 remain the canonical repository observations.
| ID | Pattern application (model 7) | Proof and residual risk |
|---|---|---|
| CS01 | Commit accepted order, durable request result, sentinel, and semantic outbox atomically; AP07 is downstream. | Reconcile every accepted order to outbox and intended consumers; relay can repeat. |
| CS02 | Receipt includes fingerprint, state, result and accepted version; query overlays it until AP07 catches up. | TTL is cleanup; ambiguous attempts need lease/repair evidence. |
| CS03 | Persist each relay attempt and inspect every PutEvents entry; retain event ID independent of bus acknowledgement. | Producer API acknowledgement is not rule, target, or business completion. |
| CS04 | Matcher writes deterministic fill, order transition, execution authority, and semantic outbox inside compatible authoritative boundaries. | Cross-account ledger/clearing work still needs inbox/outbox, saga or coordination, and reconciliation. |
| CS05 | Duplicate execution returns an effect receipt and independently resumes pending publication. | Never let a conditional duplicate short-circuit outbox repair. |
| CS06 | AP07 transaction includes inbox tradeId, expected source version, projection mutation, and receipt. | Inbox stops identity repeat; version stops stale order; neither upgrades AP07 to authority. |
| CS07 | Park future versions, alarm gap age, repair from full source, and reconcile contiguous per-account sequence. | EventBridge routing is not causal per-account order. |
| CS08 | Give each EventBridge target DLQ the required rule-scoped queue policy and operate failed-DLQ-send separately. | A DLQ is holding, not recovery; retain an independent rebuild source. |
| CS09 | Replace one-page snapshot Scan with PITR export, manifest, control totals, vNext, catch-up, and cutover. | Export is asynchronous and not one business transaction snapshot. |
| CS10 | Carry fixed minor units or exact decimals, currency, scale and governed rounding through authority/events/projections. | Projection replay must reproduce exact totals; never introduce binary-float intermediates. |
| CS11 | Add balanced immutable postings, authoritative reservations/balances, reconciliation batches, and derived portfolio/P&L. | Event store, outbox, and saga do not automatically create a ledger. |
| CS12 | Keep the matching loop on evidenced long-lived compute/sequencing; use serverless around it. | Measure latency, jitter, hot lanes, recovery and cost; no runtime label proves fit. |
- ID
- CS01
- Pattern application (model 7)
- Commit accepted order, durable request result, sentinel, and semantic outbox atomically; AP07 is downstream.
- Proof and residual risk
- Reconcile every accepted order to outbox and intended consumers; relay can repeat.
- ID
- CS02
- Pattern application (model 7)
- Receipt includes fingerprint, state, result and accepted version; query overlays it until AP07 catches up.
- Proof and residual risk
- TTL is cleanup; ambiguous attempts need lease/repair evidence.
- ID
- CS03
- Pattern application (model 7)
- Persist each relay attempt and inspect every
PutEventsentry; retain event ID independent of bus acknowledgement. - Proof and residual risk
- Producer API acknowledgement is not rule, target, or business completion.
- ID
- CS04
- Pattern application (model 7)
- Matcher writes deterministic fill, order transition, execution authority, and semantic outbox inside compatible authoritative boundaries.
- Proof and residual risk
- Cross-account ledger/clearing work still needs inbox/outbox, saga or coordination, and reconciliation.
- ID
- CS05
- Pattern application (model 7)
- Duplicate execution returns an effect receipt and independently resumes pending publication.
- Proof and residual risk
- Never let a conditional duplicate short-circuit outbox repair.
- ID
- CS06
- Pattern application (model 7)
- AP07 transaction includes inbox
tradeId, expected source version, projection mutation, and receipt. - Proof and residual risk
- Inbox stops identity repeat; version stops stale order; neither upgrades AP07 to authority.
- ID
- CS07
- Pattern application (model 7)
- Park future versions, alarm gap age, repair from full source, and reconcile contiguous per-account sequence.
- Proof and residual risk
- EventBridge routing is not causal per-account order.
- ID
- CS08
- Pattern application (model 7)
- Give each EventBridge target DLQ the required rule-scoped queue policy and operate failed-DLQ-send separately.
- Proof and residual risk
- A DLQ is holding, not recovery; retain an independent rebuild source.
- ID
- CS09
- Pattern application (model 7)
- Replace one-page snapshot Scan with PITR export, manifest, control totals,
vNext, catch-up, and cutover. - Proof and residual risk
- Export is asynchronous and not one business transaction snapshot.
- ID
- CS10
- Pattern application (model 7)
- Carry fixed minor units or exact decimals, currency, scale and governed rounding through authority/events/projections.
- Proof and residual risk
- Projection replay must reproduce exact totals; never introduce binary-float intermediates.
- ID
- CS11
- Pattern application (model 7)
- Add balanced immutable postings, authoritative reservations/balances, reconciliation batches, and derived portfolio/P&L.
- Proof and residual risk
- Event store, outbox, and saga do not automatically create a ledger.
- ID
- CS12
- Pattern application (model 7)
- Keep the matching loop on evidenced long-lived compute/sequencing; use serverless around it.
- Proof and residual risk
- Measure latency, jitter, hot lanes, recovery and cost; no runtime label proves fit.
Decision and poor-fit checklist
Section titled “Decision and poor-fit checklist”Choose the simplest step whose evidence answers “yes”:
- Is there a measured query SLO/cost/isolation problem that same-store code and indexes cannot solve?
- Is authority explicit for every balance, reservation, risk decision, order transition, execution, and posting?
- Does each projection specify freshness and overload behavior, order key, read-your-writes, duplicates, rebuild, recovery/reconciliation, and cost?
- Can retained sources cover detection + repair + catch-up, with isolated replay capacity and external effects suppressed?
- Does the break-even stay positive under lower read benefit, lower write volume, higher fan-out, incidents, and fixed on-call burden?
Stay with one model/store when reads and writes share one shape/SLO, strong current cross-domain reads dominate, volume is too low to amortize operations, or the team cannot safely rebuild. Reject event sourcing when history is not authoritative or replay/evolution cannot be governed. Reject choreography when the process needs visible durable state/human waits. Reject orchestration when a simple local transaction or two participant events are clearer. Reject every design that treats projections as monetary authority or workflow/transport semantics as exactly-once downstream effects.
Sources used in this chapter
Section titled “Sources used in this chapter”Conceptual anchors:
- Martin Fowler, CQRS and Event Sourcing (F06,F07; C35,C36,C97,C98).
- Rinat Abdullin (compiler/PDF metadata author), archived compilation of material attributed to Greg Young (F36; supporting cross-reference; no verified author-controlled original locator; C97,C98). Fowler F06/F07 remain the controlling conceptual anchors.
- Chris Richardson, Transactional Outbox and Saga (F04,F05; C38,C39,C99,C101).
- Garcia-Molina and Salem, Sagas (F10; C39,C65,C101); Hohpe and Woolf, Enterprise Integration Patterns (F02; C40,C42,C43,C100); Kleppmann and Riccomini, Designing Data-Intensive Applications (F01; C34,C41,C59,C98,C100).
- Fowler, Accounting Patterns (F08); Featonby, Making retries safe (F11); Yanacek, Avoiding insurmountable queue backlogs (F13); Google SRE (F14); FinOps Unit Economics (F19); Vogels, The Frugal Architect (F20); DeBrie, The DynamoDB Book and single-table design (F21,F22).
Current AWS semantics, all retrieved 2026-08-22:
- AWS Prescriptive Guidance: CQRS (A113), event sourcing (A114), transactional outbox (A115), and sagas (A116; retrieved 2026-08-22).
- DynamoDB consistency (A19), transactions (A20,A101), Streams (A21,A102), and export/recovery (A106; retrieved 2026-08-22).
- Step Functions workflow types, errors, and integration patterns (A25,A26,A87).
- EventBridge archive/replay (A84); SQS visibility (A85); OpenSearch service (A32); ElastiCache service (A34); Aurora replication (A35).
- Cost routes: Lambda (A44), DynamoDB (A45), Kinesis (A46), EventBridge (A47), SQS/SNS (A48), Step Functions (A49), S3 (A75), and OpenSearch (A78).
Takeaway and next step
Section titled “Takeaway and next step”Choose each pattern for the problem it actually solves: read shape, authoritative history, publication intent, or cross-owner progress. Be ready to explain both the closed boundary and what remains retryable or ambiguous. Carry one projection rebuild and one saga timeout into Reliability, correctness, and recovery, where those residual risks become owned procedures.
Reading layout adapted from SSE reading notes by Mohammed Balila, MIT. Source manifest · Attribution