Skip to main content

Command Palette

Search for a command to run...

Event Sourcing in .NET: Enterprise Decision Guide for When CRUD Isn't Enough

Published
β€’10 min read
Event Sourcing in .NET: Enterprise Decision Guide for When CRUD Isn't Enough

Most .NET enterprise systems are built on a CRUD foundation. Data flows in, rows get updated, and the current state is what the database holds. That model works reliably for the majority of applications. But there is a class of problems where the current state alone is not enough β€” where the history of how you got there has to be a first-class concern. That is the domain where Event Sourcing earns its place.

Want implementation-ready .NET source code you can adapt fast? Join Coding Droplets on Patreon. πŸ‘‰ https://www.patreon.com/CodingDroplets

The challenge for enterprise teams is not understanding the pattern in theory. It is deciding whether to adopt it, where to draw the boundary, and which tooling to trust in production. This guide gives you a decision framework grounded in real trade-offs.

What Event Sourcing Actually Changes

In a standard CRUD system, state is mutable. A record exists, and when something changes, the record reflects the new reality. The prior state is gone unless you explicitly designed an audit table.

In an Event Sourcing system, state is derived. Nothing is updated in place. Every business action that changes state is recorded as an immutable event β€” an ordered fact appended to a log. The current state of any aggregate is reconstructed by replaying all events from the beginning, or from a snapshot.

The conceptual shift is significant. Instead of asking "what is the current balance?", the system answers "what events have occurred that affect the balance, and what does that sequence total?" The event log becomes the single source of truth.

The Business Problems Event Sourcing Solves

The decision to adopt Event Sourcing should start with the problem space, not the technology. Several problem patterns justify the pattern's complexity cost.

Audit and Compliance Requirements

Financial services, healthcare, and legal platforms routinely need answers like: "What did this record look like on a specific date?" or "Who changed this field and why?" CRUD systems answer these questions by retrofitting audit logs, which are brittle and often incomplete. With Event Sourcing, auditability is structural β€” you cannot accidentally bypass it.

Complex State Machines

Booking systems, loan origination workflows, order fulfilment pipelines β€” these are systems where an entity passes through well-defined states with business rules at each transition. When the lifecycle is complex and state transitions carry meaning, representing those transitions as events is architecturally honest. Querying event history becomes a business feature rather than a debugging tool.

Temporal Queries

Some businesses need to reconstruct past state as a routine operation: "What was the account balance at close of business on the 15th?" or "What version of this configuration was active when this incident occurred?" These are impossible with mutable CRUD without purpose-built point-in-time storage. With Event Sourcing, time travel is inherent.

Event-Driven Downstream Systems

When multiple systems need to react to changes in a domain, you need a reliable event stream. In CRUD architectures, event publication is usually bolted on via the Outbox Pattern or change data capture. In Event Sourcing, the event log is the source of truth, so event-driven integration is a natural by-product, not an afterthought.

The Cases Where You Should Not Use Event Sourcing

The pattern is not universally applicable. There are contexts where CRUD is the right answer and Event Sourcing would add complexity without payoff.

Simple Reference Data

Master data tables β€” countries, product categories, configuration lookups β€” rarely need audit trails and are read far more than they are written. There is no meaningful event history here. CRUD is correct.

Reporting Aggregates

If a table's primary role is to accumulate data for reporting purposes and there is no business logic tied to its transitions, Event Sourcing adds reconstruction overhead with no return.

Teams Without Domain-Driven Design Foundation

Event Sourcing pairs tightly with DDD aggregates and bounded contexts. If the team does not yet work with those concepts consistently, introducing an Event Store will produce a poorly bounded event model that is harder to maintain than the CRUD baseline it replaced.

Time-Sensitive Greenfield Projects

When speed to market is critical and audit or temporal requirements are not on the table, Event Sourcing's steeper initial investment may not be justified for a first release.

Event Store Options in .NET

Two libraries dominate the .NET ecosystem for event-sourced systems.

EventStoreDB

EventStoreDB is a purpose-built database designed specifically for event sourcing. It uses a gRPC client for .NET and is built around the concept of streams β€” one stream per aggregate. It provides persistent subscriptions, which enable durable, at-least-once event consumption by downstream services.

EventStoreDB is the correct choice when you want a dedicated event store with first-class support for stream projections, competing consumers, and subscription management. The operational overhead of running a dedicated database must be weighed against the architectural benefits.

Marten

Marten is a PostgreSQL-backed library that provides both document store and event store capabilities for .NET. It allows teams to adopt Event Sourcing incrementally without adding new infrastructure β€” if PostgreSQL is already in the stack, Marten is available at low adoption cost.

Marten handles snapshotting, inline and async projections, and CQRS read-side models. It is the pragmatic choice for teams that want to adopt Event Sourcing without committing to a new operational tier.

Snapshots and Projection Strategies

A common performance concern with Event Sourcing is aggregate reconstruction. If an entity has thousands of events in its history, replaying every event on each read is expensive.

Snapshots address this. A snapshot captures the aggregate state at a specific event sequence number. On subsequent reads, the system loads the latest snapshot and only replays events that occurred after it. This makes high-volume event streams manageable without changing the event store structure.

Projections serve the read side. When an event is written, one or more projections transform that event data into optimised read models β€” flat denormalised tables or documents that can be queried without aggregate reconstruction. The separation of write model (events) and read model (projections) is the CQRS dimension that often accompanies Event Sourcing in enterprise systems.

Enterprise teams should plan projection strategies before adoption. Inline projections update synchronously and are consistent but add write latency. Async projections update eventually via subscriptions and are more scalable but require handling read model lag.

Integration With ASP.NET Core

Event Sourcing fits into ASP.NET Core through the same request-pipeline patterns teams already use. Commands arrive via controllers or Minimal API endpoints, are dispatched through MediatR handlers, and each handler appends one or more events to the appropriate stream. The aggregate is loaded by reading its event history, business logic is applied, and resulting events are persisted.

The registration of Marten or EventStoreDB clients follows the standard DI patterns: services are registered in Program.cs, hosted services manage subscriptions and projection runners, and health checks expose stream connectivity. Nothing in the request pipeline changes structurally.

Migration Path: Introducing Event Sourcing Into an Existing System

The riskiest path is a big-bang rewrite of an existing CRUD system. The practical path is bounded context isolation. Event Sourcing should be introduced for one specific aggregate or sub-domain with clear audit or temporal requirements. The rest of the system continues on CRUD. Over successive increments, the boundary expands where justified.

A common sequence: identify the most audit-sensitive aggregate, migrate its write side to an event model, build projections for the current read requirements, and decommission the CRUD write path once confidence is established. This allows a team to validate the operational model β€” snapshot management, projection health monitoring, subscription backpressure β€” before wider adoption.

Operational Considerations for Enterprise Teams

Event Sourcing shifts the operational model. Teams need to be prepared for concepts that do not exist in CRUD deployments.

Event schema evolution requires explicit versioning contracts. When an event's shape changes, historical events may not match the current handler. Upcasting strategies β€” transforming older event versions into current formats at read time β€” must be part of the operational model.

Projection rebuilds are a routine operational task, not an emergency procedure. When a bug in a projection is fixed, the projection must be rebuilt from the event history. Teams should automate this and test it regularly.

Read model consistency monitoring needs to be in place. If async projections fall behind β€” due to a subscription backlog or a projection failure β€” the read side becomes stale. Metrics on projection lag are essential in production.

FAQ

Is Event Sourcing the same as the Outbox Pattern? No. The Outbox Pattern is a reliability mechanism for publishing domain events from a CRUD system without losing them on failure. Event Sourcing replaces the mutable state model entirely β€” the event log is the source of truth, not a companion to a mutable store.

Can I use Event Sourcing with Entity Framework Core? EF Core is a CRUD ORM and is not designed for event sourcing workflows. Marten or EventStoreDB clients are the appropriate tools. Some teams keep EF Core for non-event-sourced aggregates in the same application, but the two models should be bounded by context and not mixed for the same aggregate.

How does Event Sourcing affect integration testing? It simplifies integration testing in practice. Tests can seed the event store with a known sequence of events and assert that the resulting aggregate state or projection is correct. There is no need to set up mutable database state β€” the event sequence is the test fixture.

What is the difference between an event and a domain event in DDD? A domain event is a notification that something happened within a bounded context, typically published to notify other contexts. In Event Sourcing, events are stored facts that define aggregate state β€” they are the write model. The distinction matters because not all stored events need to be published externally, and not all published domain events are stored as first-class state.

How should event schema versioning be handled in .NET? The standard approach is upcasting: register a mapping from an older event type to its current representation. At read time, the deserialiser recognises the event type version and applies the upcast before passing it to the aggregate handler. Both EventStoreDB and Marten have documented approaches for this. Avoid schema changes that delete fields without an upcast strategy in place.

When should I use inline projections vs async projections? Inline projections are appropriate when the read model must be consistent with the write at the point of command completion β€” for example, when the API response immediately returns data from the read model. Async projections are better for scale-sensitive scenarios where eventual consistency is acceptable, as they decouple projection computation from the write path and can be scaled independently.

Is Event Sourcing a good fit for multi-tenant SaaS on .NET? Yes, with careful design. Tenant isolation can be enforced at the stream level β€” each stream identifier is scoped to a tenant, preventing cross-tenant aggregate contamination. Projections must also enforce tenant scope. The audit trail that Event Sourcing provides is often a compliance requirement in enterprise SaaS contracts, making the pattern well-aligned with multi-tenant scenarios.

More from this blog

C

Coding Droplets

119 posts