CQRS and Event Sourcing for .NET. Start small, keep the receipts.

Begin with a lean mediator. Add an event store when you need one. Scale out with an outbox, projections and sagas when you must. One MIT-licensed family of packages, one version, and tamper-evident streams and tenant-bound encryption already inside.

Latest release on NuGet MIT licence .NET 10

New to the terms? Thirty seconds, then pick a door.

Mediator

A controller that knows ten services is hard to test and harder to change. With a mediator it hands over one object — OpenAccount — and a dispatcher finds the single handler that answers it. One request, one handler, and no web server needed to test it.

CQRS

Command Query Responsibility Segregation. A command changes something and returns little; a query reads and changes nothing. Keeping them apart lets each side take the shape and the scaling its own job needs. It is a routing decision first, not a second database.

Event sourcing

Store the facts that happened — AccountOpened, MoneyDeposited — instead of the state they produced. Current state is a fold over those facts, so the history is the source of truth rather than an audit log kept beside it.

Full glossary →  ·  Why event sourcing →

Pick your door

Three reasons people arrive here. Each one is a real entry point, not a teaser for the whole stack.

I need a mediator

Commands, queries and pipeline behaviors, in process, MIT. Nothing else comes along.

dotnet add package Stratara.Mediator
public sealed record OpenAccount(string Owner)
    : ICommand<Guid>;

public sealed class OpenAccountHandler
    : IQueryHandler<OpenAccount, Guid>
{
    public Task<Guid> HandleAsync(
        OpenAccount cmd, CancellationToken ct)
        => Task.FromResult(Guid.NewGuid());
}

builder.Services
    .AddMediator()
    .AddQueryHandlersFromAssemblyContaining<Program>();

You do not need: a database, a broker, or any telemetry setup.

First Stratara app →

I want event sourcing without the plumbing

Aggregates and events on PostgreSQL, snapshots, outbox, projections, replay — shipped, not sketched.

dotnet add package Stratara.EventSourcing.WorkerDefaults
public sealed record InvoiceIssued(
    Guid InvoiceId, Guid TenantId, decimal Total)
    : IAggregateCreationEvent;

public sealed class Invoice : ITenantAggregate
{
    public Guid Id { get; set; }
    public Guid TenantId { get; set; }
    public decimal Total { get; set; }

    public void Apply(InvoiceIssued e) =>
        (Id, TenantId, Total) =
        (e.InvoiceId, e.TenantId, e.Total);
}

// inside a command handler
await events.CreateAsync<Invoice>(
    id, new InvoiceIssued(id, tenantId, 120m), ct);
await events.SaveChangesAsync(ct);

You do not need: to write an event store, an outbox or a replay. They are the product.

Event-sourced walkthrough →

I run a multi-tenant SaaS and get audited

Hash-chained streams, fields sealed to their tenant, and GDPR erasure by destroying a key.

dotnet add package Stratara.Security
public sealed record CustomerRegistered(
    Guid CustomerId,
    Guid TenantId,
    [property: EncryptData] string Email)
    : IAggregateCreationEvent;

// Right to erasure: shred the subject's key.
// Events, snapshots, replicas and backups
// all become noise.
await keyStore.EraseScopeAsync(scope, ct);

You do not need: a separate audit product, or a WHERE tenant_id you hope nobody forgets.

Tamper-evident streams →

It grows with you

The same handler you wrote on day one runs unchanged on day three hundred. Only the hosting around it changes.

Three stages: a mediator alone; the mediator plus an event store on PostgreSQL; the full stack with API hosts, a message bus, command and projection workers, the event store and read models.
Stage 1 is one package. Stage 2 adds PostgreSQL. Stage 3 adds a broker and scales out as competing consumers.

What is in the box

Integrated, not assembled. Every part below is versioned together and tested against the others.

Mediator and pipeline

Commands, queries, open-generic behaviors in registration order, authorization and tenant isolation at the entrance.

Event store on PostgreSQL

Streams, snapshots, optimistic concurrency, event upcasting, command audit — through EF Core you already run.

Outbox and messaging

At-least-once dispatch over RabbitMQ or Azure Service Bus, publisher confirms, a heavy-command lane.

Projections and sagas

Push-driven from the event bus, per-aggregate ordering, retry for facts that arrive before their beginning.

Tamper-evident streams

Every event hash-chained, with anchors you can pin outside your database. Edit a row and the chain names the sequence.

Tenant-bound encryption

AES-GCM with the tenant as associated data; a row leaked from one tenant cannot be read in another.

Identity and membership

Users in many tenants with per-membership roles, a code-first permission catalog, API keys that share the same plane.

Observability defaults

One activity source, one meter, stable log-event ids, OpenTelemetry and Serilog wired in a line.

Numbers, not adjectives

Measured with BenchmarkDotNet on a fanless MacBook Air M4. Read them as conservative ratios, not a tuned server's ceiling.

11.6 ms
to replay one million events in memory
64 B
allocated for that replay, regardless of length
13×
faster property writes than reflection, allocation-free
< 1 µs
per event for tamper-evident chain hashing

Methodology and caveats →

Where it sits

An honest map. Each project below is good at what it does; this is about scope and license, not ranking.

ProjectScopeLicenseApproach
StrataraMediator, event store, outbox, projections, sagas, identity, encryptionMITOne lockstep family; opt in per package; audit properties are defaults, not add-ons
MediatRIn-process mediatorRPL-1.5 or commercial from v13; free Community edition below 5 M USD revenueThe reference mediator; bring your own everything else
Marten + WolverineDocument DB and event store on PostgreSQL; messaging and handlersMIT, open core with commercial supportTwo libraries that compose well; broad and mature
MassTransitDistributed messaging, sagasv9 commercial since 2026; v8 Apache 2.0, supported to end of 2026Transport-centric; no event store
KurrentDB (EventStoreDB)Purpose-built event databaseVendor licenseA server you operate, not a library you reference

License facts verified September 2026 against each project's published terms. Check the source before you decide.

Five minutes to a running mediator. An afternoon to event sourcing.

Every guarantee on this page is written down as a specification and tested in CI. Read them, run the samples, then decide.

Derived. The behaviour described on this page is specified under openspec/specs/ in the repository. Those specifications are the source; this page explains and illustrates them.