Skip to main content

Event Store Providers

Each event store provider implements IEventStore with database-specific optimizations. Choose the provider that matches your database.

Quick Start

Pick your database and copy the registration:

DatabasePackageRegistration
SQL ServerExcalibur.EventSourcing.SqlServeres.UseSqlServer(opts => opts.ConnectionString = connStr)
PostgreSQLExcalibur.EventSourcing.Postgresservices.AddPostgresEventSourcing(opts => opts.ConnectionString = connStr)
MongoDBExcalibur.Data.MongoDBservices.AddMongoDbSnapshotStore(opts => { ... })
Cosmos DBExcalibur.Data.CosmosDbservices.AddCosmosDb(opts => { ... })
DynamoDBExcalibur.Data.DynamoDbservices.AddDynamoDb(opts => { ... })
FirestoreExcalibur.Data.Firestoreservices.AddFirestore(opts => { ... })
In-MemoryExcalibur.EventSourcing.InMemoryes.UseInMemory() (builder only)

Each AddXxxEventSourcing() call registers IEventStore and ISnapshotStore for that provider. Outbox is registered separately via AddExcaliburOutbox().

Before You Start

SQL Server

The primary event store provider with full transaction support.

Installation

dotnet add package Excalibur.EventSourcing.SqlServer

Setup

using Microsoft.Extensions.DependencyInjection;

// Recommended: Builder-integrated registration
services.AddExcaliburEventSourcing(es =>
{
es.UseSqlServer(opts => opts.ConnectionString = connectionString)
.AddRepository<OrderAggregate, Guid>();
});

// Or with detailed options
services.AddExcaliburEventSourcing(es =>
{
es.UseSqlServer(options =>
{
options.ConnectionString = connectionString;
options.EventStoreSchema = "es";
options.SnapshotStoreSchema = "es";
options.OutboxSchema = "es";
});
});

// Alternative: Direct IServiceCollection registration (event store + snapshots only)
services.AddSqlServerEventSourcing(opts => opts.ConnectionString = connectionString);

// Individual stores
services.AddSqlServerEventStore(opts => opts.ConnectionString = connectionString);
services.AddSqlServerSnapshotStore(opts => opts.ConnectionString = connectionString);

// With connection factory
services.AddSqlServerEventStore(() => new SqlConnection(connectionString));
services.AddSqlServerSnapshotStore(() => new SqlConnection(connectionString));

// With typed IDb marker (multi-database scenarios)
services.AddSqlServerEventStore<IOrderDb>();
services.AddSqlServerSnapshotStore<IOrderDb>();
services.AddSqlServerEventSourcing<IOrderDb>(); // registers event store + snapshots

// Outbox is registered separately via the unified outbox package
services.AddExcaliburOutbox(outbox => outbox.UseSqlServer(connectionString));

PostgreSQL

Open-source alternative with Npgsql-based access.

Installation

dotnet add package Excalibur.EventSourcing.Postgres

Setup

// Recommended: Builder-integrated registration
services.AddExcaliburEventSourcing(es =>
{
es.UsePostgres(opts => opts.ConnectionString = connectionString)
.AddRepository<OrderAggregate, Guid>();
});

// Or with options
services.AddExcaliburEventSourcing(es =>
{
es.UsePostgres(options =>
{
options.ConnectionString = connectionString;
options.HealthChecks.RegisterHealthChecks = true;
});
});

// Alternative: Direct IServiceCollection registration
services.AddPostgresEventStore(options =>
{
options.ConnectionString = connectionString;
options.SchemaName = "events";
options.EventsTableName = "event_store_events"; // Default
});

// With NpgsqlDataSource (recommended for connection pooling)
var dataSource = NpgsqlDataSource.Create(configuration.GetConnectionString("Postgres")!);
services.AddPostgresEventSourcing(dataSource);

Projection Store

Register a Postgres-backed projection store for read models:

// With connection string
services.AddPostgresProjectionStore<OrderSummaryProjection>(options =>
{
options.ConnectionString = connectionString;
options.TableName = "order_summaries"; // Optional: defaults to snake_case type name
});

// With NpgsqlDataSource (recommended for connection pooling)
services.AddPostgresProjectionStore<OrderSummaryProjection>(
dataSourceFactory: sp => sp.GetRequiredService<NpgsqlDataSource>(),
configureOptions: options =>
{
options.TableName = "order_summaries";
});

PostgresProjectionStoreOptions properties:

PropertyTypeDefaultDescription
ConnectionStringstring?RequiredPostgres connection string
TableNamestring?Type name (snake_case)Table name for projections
JsonSerializerOptionsJsonSerializerOptions?camelCase, no indentJSON serializer options for projection data

CockroachDB and YugabyteDB Compatibility

The Postgres provider works with CockroachDB and YugabyteDB out of the box -- both databases are PostgreSQL wire-compatible and work with Npgsql. No code changes or additional packages are needed.

// CockroachDB
services.AddExcaliburEventSourcing(es =>
{
es.UsePostgres(opts =>
opts.ConnectionString = "Host=cockroachdb.example.com;Port=26257;Database=events;...");
});

// YugabyteDB
services.AddExcaliburEventSourcing(es =>
{
es.UsePostgres(opts =>
opts.ConnectionString = "Host=yugabyte.example.com;Port=5433;Database=events;...");
});

Known considerations:

DatabaseDefault PortNotes
PostgreSQL5432Full feature support
CockroachDB26257Distributed SQL. SERIALIZABLE isolation by default (stricter than Postgres READ COMMITTED).
YugabyteDB5433Distributed SQL. Compatible with Postgres extensions. Supports NpgsqlDataSource pooling.

All three use the same Excalibur.EventSourcing.Postgres package, DDL, and query paths. Tenant sharding (UsePostgresTenantEventStore) and parallel catch-up (PostgresRangeQueryEventStore) also work with wire-compatible databases.

tip

For CockroachDB, set options.SchemaName = "public" (CockroachDB does not support custom schemas in the same way as PostgreSQL). For YugabyteDB, the default public schema works as expected.


Azure Cosmos DB

Globally distributed event store with partition-based scaling.

Installation

dotnet add package Excalibur.EventSourcing.CosmosDb

Setup

// Recommended: Builder-integrated registration
services.AddExcaliburEventSourcing(es =>
{
es.UseCosmosDb(options =>
{
options.ConnectionString = connectionString;
options.DatabaseName = "events";
options.ContainerName = "event-store";
})
.AddRepository<OrderAggregate, Guid>();
});

// Or with IConfiguration binding
services.AddExcaliburEventSourcing(es =>
{
es.UseCosmosDb(configuration.GetSection("CosmosDb"));
});

// Alternative: Direct registration
services.AddCosmosDbEventStore(options =>
{
options.EventsContainerName = "events";
options.PartitionKeyPath = "/streamId"; // Default
});

Partition Strategy

Cosmos DB event stores partition by aggregate ID. Each aggregate's events are stored in a single logical partition for transactional consistency.


Amazon DynamoDB

Serverless event store for AWS workloads.

Installation

dotnet add package Excalibur.EventSourcing.DynamoDb

Setup

// Recommended: Builder-integrated registration
services.AddExcaliburEventSourcing(es =>
{
es.UseDynamoDb(options =>
{
options.TableName = "event-store";
options.Region = "us-east-1";
})
.AddRepository<OrderAggregate, Guid>();
});

// Or with IConfiguration binding
services.AddExcaliburEventSourcing(es =>
{
es.UseDynamoDb(configuration.GetSection("DynamoDb"));
});

// Alternative: Direct registration
services.AddDynamoDbEventStore(options =>
{
options.EventsTableName = "Events";
});

Key Schema

DynamoDB event stores use the aggregate ID as the partition key and event version as the sort key, providing efficient sequential reads per aggregate.


Google Firestore

Real-time event store for Google Cloud workloads.

Installation

dotnet add package Excalibur.EventSourcing.Firestore

Setup

// Recommended: Builder-integrated registration
services.AddExcaliburEventSourcing(es =>
{
es.UseFirestore(options =>
{
options.ProjectId = "my-gcp-project";
options.CollectionName = "events";
})
.AddRepository<OrderAggregate, Guid>();
});

// Or with IConfiguration binding
services.AddExcaliburEventSourcing(es =>
{
es.UseFirestore(configuration.GetSection("Firestore"));
});

// Alternative: Direct registration
services.AddFirestoreEventStore(options =>
{
options.ProjectId = "my-gcp-project";
options.EventsCollectionName = "events";
});

Collection Structure

Firestore event stores use subcollections under aggregate documents, leveraging Firestore's hierarchical document model.


MongoDB

Document-oriented event store with flexible schema and horizontal scaling via sharding.

Installation

dotnet add package Excalibur.EventSourcing.MongoDB

Setup

// Recommended: Builder-integrated registration
services.AddExcaliburEventSourcing(es =>
{
es.UseMongoDB(options =>
{
options.ConnectionString = "mongodb://localhost:27017";
options.DatabaseName = "events";
})
.AddRepository<OrderAggregate, Guid>();
});

// Or with connection string shorthand
services.AddExcaliburEventSourcing(es =>
{
es.UseMongoDB("mongodb://localhost:27017", "events")
.AddRepository<OrderAggregate, Guid>();
});

// Alternative: Direct IServiceCollection registration
services.AddMongoDbEventStore(options =>
{
options.ConnectionString = "mongodb://localhost:27017";
options.DatabaseName = "EventStore";
options.CollectionName = "events";
});

// With custom client factory (receives IServiceProvider)
services.AddMongoDbEventStore(
sp => sp.GetRequiredService<IMongoClient>(),
options =>
{
options.DatabaseName = "EventStore";
});

Document Model

MongoDB event stores use a single collection per aggregate type with the aggregate ID as the document key. Events are stored as embedded arrays within the aggregate document.


SQLite (Local Development)

Zero-Docker local development and testing. Auto-creates tables on first use.

Installation

dotnet add package Excalibur.EventSourcing.Sqlite

Setup

services.AddExcaliburEventSourcing(es =>
{
es.UseSqlite(options =>
{
options.ConnectionString = "Data Source=events.db";
});
});

Registers both IEventStore and ISnapshotStore backed by SQLite.

OptionDefaultDescription
ConnectionStringRequiredSQLite connection string (e.g., Data Source=events.db)
EventStoreTable"Events"Table name for events
SnapshotStoreTable"Snapshots"Table name for snapshots
When to use SQLite

SQLite is ideal for local development, quick prototyping, and unit/integration tests where you want a real database without Docker. For production workloads, use SQL Server, PostgreSQL, or a cloud provider.


In-Memory (Testing)

For unit and integration tests:

// Recommended: Builder-integrated registration
services.AddExcaliburEventSourcing(es =>
{
es.UseInMemory()
.AddRepository<OrderAggregate, Guid>();
});

// Alternative: Direct registration
services.AddInMemoryEventStore();

Provider Comparison

ProviderPackageTransaction SupportScaling Model
SQL ServerExcalibur.EventSourcing.SqlServerFull ACIDVertical + read replicas
PostgreSQLExcalibur.EventSourcing.PostgresFull ACIDVertical + read replicas
MongoDBExcalibur.EventSourcing.MongoDBDocument-levelSharding
Cosmos DBExcalibur.EventSourcing.CosmosDbPartition-scopedGlobal distribution
DynamoDBExcalibur.EventSourcing.DynamoDbItem-levelOn-demand / provisioned
FirestoreExcalibur.EventSourcing.FirestoreDocument-levelAutomatic
SQLiteExcalibur.EventSourcing.SqliteFull ACID (single-writer)Single process
In-MemoryExcalibur.EventSourcing.InMemoryNoneSingle process

Batch Projection Registration

When registering multiple projections for the same provider, use the batch registrar API instead of individual AddXxxProjectionStore<T>() calls:

// SQL Server: register multiple projections sharing the same connection
services.AddSqlServerProjections(connectionString, projections =>
{
projections.Add<OrderSummary>();
projections.Add<CustomerProfile>(o => o.TableName = "CustomerViews");
});

// MongoDB
services.AddMongoDbProjections(connectionString, "MyApp", projections =>
{
projections.Add<OrderSummary>();
projections.Add<CustomerProfile>(o => o.CollectionName = "customers");
});

// CosmosDB
services.AddCosmosDbProjections(connectionString, "MyDatabase", projections =>
{
projections.Add<OrderSummary>();
});

// PostgreSQL
services.AddPostgresProjections(connectionString, projections =>
{
projections.Add<OrderSummary>();
});

// ElasticSearch
services.AddElasticSearchProjections("https://es.example.com:9200", projections =>
{
projections.Add<OrderSummary>();
});

See Data Providers for provider-specific details and naming conventions.

Cold Event Store Providers (Tiered Storage)

For hot/cold storage separation at petabyte scale, archived events are moved from the primary (hot) store to a cold store in blob/object storage. All cold store providers implement IColdEventStore (4 methods: WriteAsync, ReadAsync, ReadAsync(fromVersion), HasArchivedEventsAsync) and use a gzip-compressed JSON format.

Azure Blob Storage

dotnet add package Excalibur.EventSourcing.AzureBlob
services.AddExcaliburEventSourcing(builder =>
{
builder.UseAzureBlobColdEventStore(opts =>
{
opts.ConnectionString = "DefaultEndpointsProtocol=https;...";
opts.ContainerName = "event-archive";
opts.BlobPrefix = "events";
});
});

AWS S3

dotnet add package Excalibur.EventSourcing.AwsS3
services.AddExcaliburEventSourcing(builder =>
{
builder.UseAwsS3ColdEventStore(opts =>
{
opts.BucketName = "my-event-archive";
opts.Region = "us-east-1";
opts.KeyPrefix = "events";
});
});

Google Cloud Storage

dotnet add package Excalibur.EventSourcing.Gcs
services.AddExcaliburEventSourcing(builder =>
{
builder.UseGcsColdEventStore(opts =>
{
opts.BucketName = "my-event-archive";
opts.ObjectPrefix = "events";
});
});

Cold Store Comparison

ProviderPackageAuthentication
Azure BlobExcalibur.EventSourcing.AzureBlobConnection string or DefaultAzureCredential
AWS S3Excalibur.EventSourcing.AwsS3AWS SDK default credential chain
GCSExcalibur.EventSourcing.GcsGoogle Application Default Credentials

All providers store events as {prefix}/{aggregateId}/events.json.gz and support merge-on-write (read existing, append new, write back).

Archive Metrics

Meter: Excalibur.EventSourcing.Archive

MetricTypeDescription
excalibur.eventsourcing.archive.events_archivedCounterEvents moved to cold storage
excalibur.eventsourcing.archive.events_deletedCounterEvents removed from hot store
excalibur.eventsourcing.archive.cold_readsCounterRead-through operations from cold
excalibur.eventsourcing.archive.errorsCounterArchive operation failures
excalibur.eventsourcing.archive.duration_secondsHistogramBatch archive duration

See Also