Official .NET client for Celeriant: the distributed event store built for event sourcing at scale.
Celeriant is an event store that lets you enforce business invariants at write time across multiple streams, without distributed transactions. Optimistic concurrency control, strict event ordering, exactly-once writes, schema validation, and cluster-wide durability. PostgreSQL gives you correctness but not throughput. Kafka gives you throughput but not correctness. Celeriant gives you both.
Targets net8.0, net9.0, and net10.0. Dependencies are Celeriant.Transport (shared wire framing, published alongside), MessagePack for serialisation, and ZstdSharp for compression.
For a deeper walkthrough: aggregate modelling, schemas, watch API, connection pool internals: see the usage guide.
dotnet add package Celeriant.ClientVersion 0.10 needs Celeriant server 0.3 or later and refuses to talk to older servers, so upgrade the server first. Celeriant.Client and Celeriant.Transport ship together at the same version; let Celeriant.Client pull in Transport rather than pinning it separately.
Celeriant uses io_uring, so the container needs seccomp=unconfined.
docker run -d --name celeriant \
--security-opt seccomp=unconfined \
-p 10000:10000 \
ghcr.io/celeriant/celeriant \
--standalone --data-root /var/lib/celeriant --num-shards 1await using var client = await CeleriantClient.ConnectAsync("localhost:10000");
var serializer = JsonEventSerializer.Default;
var key = new AggregateKey(orgId: myOrg, aggregateTypeId: myType, aggregateId: orderId);
await client.WriteAsync(key, [
AggregateEventExtensions.Create(eventTypeMajor: 1, new OrderPlaced(orderId, 99.95m), serializer)
], clientId: myClientId);var response = await client.ReadAsync(new ReadRequest
{
AggregateKey = key,
Filters = ReadFilters.From(1)
});
var order = response.EventBatches[0].Events[0].GetValue<OrderPlaced>(serializer);For production use, CeleriantPool manages connections, routes writes to the leader, and distributes reads across nodes. It implements ICeleriantPool for easy testing and DI.
await using var pool = new CeleriantPool(new CeleriantPoolOptions
{
Address = "localhost:10000",
MaxConnections = 20,
});
await pool.WriteAsync(key, events, myClientId);
var read = await pool.ReadAsync(readRequest);The pool also supports DeleteAsync, TrimStartAsync, AggregateDetailsAsync, RegisterSchemaAsync, WatchAsync, ReadAllAsync (streaming), and listing operations (ListOrgsAsync, ListAggregateTypesAsync, ListAggregatesAsync).
With DI (registers ICeleriantPool as a singleton):
builder.Services.AddCeleriantPool(options =>
{
options.Address = "localhost:10000";
options.MaxConnections = 20;
});Then inject ICeleriantPool into your services:
public class MyService(ICeleriantPool pool)
{
public Task DoWork() => pool.ReadAsync(...);
}var tls = ClientTlsConfig.WithClientCertificateFromPem("localhost", "client.crt", "client.key");
await using var client = await CeleriantClient.ConnectTlsAsync("localhost:10010", tls);To run the TLS integration tests locally:
test-certs/generate.sh
docker compose -f docker-compose.tls.yml up -d
dotnet test- Celeriant.Demo: simple browser-based banking demo.
cd Celeriant.Demo && docker compose up -dto start everything. Shows basic read/write patterns with a minimal UI. - Celeriant.Reference: production-grade reference API with Postgres read projections, exactly-once writes, OCC retry loops, and multi-aggregate transfers.
cd Celeriant.Reference && docker compose up -dto start everything.
# unit tests only
dotnet test Celeriant.Client.Tests
# integration tests (requires docker compose up -d)
dotnet test Celeriant.Client.IntegrationTestsApache 2.0