Skip to content

Event sourcing in .NET

Chronicle is an open-source (MIT) event-sourcing database and processing runtime, and .NET is its first-class client platform. The Cratis.Chronicle SDK gives you typed access to event stores, event logs, read models, projections, reducers, reactors, constraints, migrations, namespaces, and hosting integration — including a one-line .NET Aspire resource. You define events as C# records, append them to an event log, and Chronicle takes care of storage, processing, and delivering derived read models.

Instead of storing only the current state, every state change in your system is captured as an immutable event. That gives you a full history to audit, replay, and build new views from — while the SDK keeps the developer experience convention-based: events, reducers, reactors, and projections are discovered automatically and registered with the kernel when your application connects.

Chronicle’s kernel runs on Microsoft Orleans and stores events in pluggable storage — MongoDB by default, with PostgreSQL, SQL Server, SQLite, and in-memory providers implemented in the kernel. The same store is also reachable from TypeScript, Kotlin/Java, and Elixir clients over Chronicle’s language-agnostic gRPC contract, so a .NET service and services in other languages can share one event store.

For an ASP.NET Core application, which is what the example below uses:

Terminal window
dotnet add package Cratis.Chronicle.AspNetCore

It brings in the core Cratis.Chronicle SDK. A console app or worker service uses Cratis.Chronicle on its own; the get started guides cover each hosting model.

This excerpt shows the shape of the API. It assumes a Chronicle server is running locally; Get started with Chronicle walks through starting one and building a complete app.

Hook Chronicle into an ASP.NET Core application:

var builder = WebApplication.CreateBuilder(args)
.AddCratisChronicle(options => options.EventStore = "MyApp");
var app = builder.Build();
app.UseCratisChronicle();
app.Run();

Define an event as a record:

[EventType]
public record UserOnboarded(string Name, string Email);

Append it through an injected IEventLog, for example in a minimal API endpoint:

await eventLog.Append(Guid.NewGuid(), new UserOnboarded("Jane Doe", "jane@example.com"));

From there, reducers fold events into read models, reactors run side effects when events arrive, and declarative projections describe read models without writing the fold by hand.

Looking for another language? See Chronicle in your language for the TypeScript, Kotlin/Java, and Elixir clients.