Insights & Blog

CQRS and Event Sourcing in .NET: When to Use Them and When Not To

Separate CQRS from Event Sourcing in .NET. Review consistency, snapshots, projection lag, replay and operational responsibilities with a reference architecture and decision checklist.

By Hetal KIT Solution

CQRS and Event Sourcing workflow: validate and commit commands, project committed events, and recover through validated snapshots and safe replay.

CQRS and Event Sourcing in .NET: When to Use Them and When Not To

CQRS separates command and query responsibilities; Event Sourcing stores business state as an authoritative sequence of events. A .NET application can use CQRS without Event Sourcing, separate databases or microservices. Adopt the additional machinery only when the workload’s rules, history and operating requirements justify it.

Key takeaways

  • Separating read and write models does not require separate physical stores.
  • Event history as the system of record is different from an audit log beside mutable state.
  • Projection lag, concurrency, event evolution and replay require explicit ownership.
  • Snapshots accelerate rehydration; they do not replace authoritative history.

CQRS event sourcing .NET: three different architecture choices

Microsoft’s CQRS guidance distinguishes separate read and write models within one store from designs with separate stores. Its Event Sourcing guidance describes event history as the authoritative record and warns that the pattern introduces substantial complexity. Use these as separate decisions.

ChoiceState authorityRead pathAdded responsibilities
Conventional CRUDCurrent-state recordsQueries against the application model or databaseOrdinary data integrity and access controls
CQRS with current-state persistenceCurrent-state write modelSeparate query models, in the same or another storeModel ownership and any projection synchronization
CQRS with Event SourcingOrdered business event streamsProjections derived from committed eventsEvent evolution, rehydration, concurrency, replay and recovery

A mediator library can dispatch requests without creating a robust CQRS design. Publishing notifications after a database update does not automatically make a system event-sourced. The essential questions are where truth lives and how read results are derived.

Keep current-state persistence when it meets requirements. If the only problem is a complex reporting query, a dedicated query model may solve it without replacing the application’s state model.

A reference architecture with explicit commit boundaries

Command + identity + operation ID
→ authorize and validate
→ load stream (optionally snapshot + later events)
→ evaluate business invariants
→ append events with expected stream version
→ acknowledge durable commit

Committed events → reliable subscription → idempotent projector
→ query-optimized view → authorized query endpoint

External effects use a separate owned delivery/reconciliation path.
Rebuilding a projection must not resend business actions.

This conceptual architecture does not mandate a particular event-store product or transport. Verify the chosen store’s append guarantees, version checks, subscription behavior and recovery features. Do not acknowledge a successful command merely because an event was placed in an in-memory queue.

Keep event types business-oriented, such as OrderSubmitted or ShipmentConfirmed. Include enough stable meaning to reconstruct the relevant state without making every event a copy of an internal object. Review sensitive data before putting it into long-lived history.

Use explicit correlation and causation metadata where it helps investigations. Store operation identity according to the duplicate-handling contract. A correlation ID is useful for tracing but is not automatically an idempotency guarantee.

Consistency, duplicate delivery and concurrent commands

Separate command success from read-view freshness. A write can be durably committed while a projection is still behind. Design the user experience to show pending processing or an authoritative command result instead of silently presenting an old view as proof that nothing happened.

Define a measurable projection freshness objective and how users or support staff identify lag. A system that tolerates eventual consistency still needs a bound on operationally acceptable delay and a response when that bound is exceeded.

For a concurrency example, two commands load the same stream version. Both may pass business validation against that state. The store must apply the chosen concurrency rule so conflicting appends cannot both succeed as if no intervening change occurred. A rejected command may need reload and reevaluation, not an unconditional retry of the same proposed events.

Projectors should tolerate duplicate delivery. Track event identity or a reliable stream position and update the read model with an appropriate transactional boundary. Advancing a checkpoint before the projection is safely stored can lose work; storing a result without safe duplicate handling can repeat it after a crash.

Do not claim exactly-once business behavior merely because a broker offers a delivery feature. Database changes, checkpoints and external actions have separate failure boundaries. Test restart and replay behavior with the actual components.

Snapshots, event evolution and safe replay

A snapshot records derived state at a known stream version. Rehydration can load it and apply later events. Keep snapshot schema and stream position explicit, and prove that the result matches replay from authoritative events.

A stale snapshot can be valid if all later events are applied correctly. A corrupt or incompatible snapshot should have an owned recovery path. Avoid using a snapshot as the only recoverable copy while describing the event log as authoritative.

Event contracts outlive the application version that wrote them. Plan how newer readers interpret older events, how projection logic changes and which historical meanings must remain stable. Version handling and upcasting are design options, not excuses to rewrite history without review.

Separate projection reconstruction from side-effect processing. Replaying an order history to rebuild a dashboard must not send another invoice, refund or customer message. Run rebuilds against isolated targets, validate results, then promote the new read model through a controlled process.

Review retention, access and sensitive-data obligations with the relevant owners before adopting permanent history. Event Sourcing does not automatically resolve audit, privacy or records-management requirements.

Worked example: order status and a delayed read model

Consider a fictional order application. A submit command passes authorization and commits OrderSubmitted at version 12. The query projection is still at version 11. This is an illustrative walkthrough, not a completed customer implementation.

The API returns the committed operation result with a status reference. The UI can acknowledge submission while showing that its list view is updating. It does not ask the user to submit again solely because the old projection has not caught up.

The projector receives version 12, writes the view and records progress using the chosen consistency mechanism. A retry of the same event does not create a duplicate order row. Operations can inspect the latest committed and processed positions to distinguish a write failure from projection lag.

Later, two cancellation attempts start from the same stream version. The expected-version check allows only the valid append under the chosen concurrency contract. The other command reloads current state and produces the appropriate business response. It does not blindly replay an obsolete decision.

During a new dashboard rollout, the team rebuilds a separate view from history with external effects disabled. It validates totals and statuses before switching queries. Snapshot-based rehydration is checked against full replay on selected streams. These are proposed acceptance scenarios, not benchmark results.

When is the complexity justified?

Observed requirementCandidate directionQuestion before approval
Simple CRUD and straightforward reportingCurrent-state persistenceWhat problem would extra models solve?
Different read shapes and write rulesCQRS, initially with the simplest suitable persistenceCan model separation solve the need without event history?
Authoritative historical reconstruction is centralEvaluate Event Sourcing for the bounded capabilityCan the team own event evolution and replay?
Independent read scaling is necessaryConsider separate read storesWhat freshness and recovery contract can users accept?
No owner for projections or historyResolve operations before adoptionWho repairs lag, corruption or incompatible events?

Adopt the pattern for one justified capability rather than assuming the entire application must be event-sourced. A modular application can contain both event-sourced and current-state modules behind clear contracts.

Production readiness checklist

  • Authoritative state and durable acknowledgment are documented.
  • Concurrency conflicts and duplicate commands have defined outcomes.
  • Projection lag is measured and the user experience handles it.
  • Replay, snapshot validation and event evolution are tested.
  • External actions are isolated from read-model reconstruction.
  • Backups, restores, permissions and incident ownership are rehearsed.

Evaluate these responsibilities alongside the modular monolith implementation guide and feature and dependency boundary comparison. Neither pattern requires deploying a separate microservice for every command.

Common questions

When is CQRS worth the complexity in .NET?

When distinct read and write responsibilities simplify real workload requirements. Start with the least complex persistence model that solves the problem.

Does CQRS require Event Sourcing?

No. Commands and queries can use separate models over a current-state database. Event Sourcing is an additional state-management decision.

Are snapshots the source of truth?

In an event-sourced design, they are derived acceleration artifacts. Authoritative events and a tested recovery process must support reconstruction.

Can replay resend external actions?

It can if the design mixes projections and side effects. Isolate reconstruction from action delivery and test that replay does not repeat business actions.

Does Event Sourcing remove concurrency problems?

No. Concurrent commands still require an explicit version or consistency mechanism and business-aware conflict handling.

Choose state patterns from business and operating needs

Bring your write rules, reporting needs and historical reconstruction requirements. Discuss your .NET modernization roadmap to assess CQRS, Event Sourcing and a manageable implementation boundary.

From insight to implementation

Turn these ideas into a practical plan

Discuss how the ideas in this article apply to your business, systems and data. We’ll help you identify a focused first step.