Configuration
This page lists every option you can configure in Nagare, organized by package.
Database connection
Before registering event stores or projections, set up a database connection. Each adapter has its own registration method:
// PostgreSQL
builder.Services.AddNagarePostgresStorage(builder.Configuration);
// SQL Server
builder.Services.AddNagareSqlServerStorage(builder.Configuration);
// SQLite
builder.Services.AddNagareSqliteStorage(builder.Configuration);
// MySQL
builder.Services.AddNagareMySqlStorage(builder.Configuration);All three read a connection string from IConfiguration. By default, they look for "DbConnectionString" in the connection strings section:
{
"ConnectionStrings": {
"DbConnectionString": "Host=localhost;Database=myapp;Username=postgres;Password=secret"
}
}You can pass a different name:
builder.Services.AddNagarePostgresStorage(
builder.Configuration,
connectionStringName: "EventStoreDb");Event store
Register an event store for each event type in your system:
// PostgreSQL
builder.Services.AddPostgresEventStore<BookEvent>();
// SQL Server
builder.Services.AddSqlServerEventStore<BookEvent>();
// SQLite
builder.Services.AddSqliteEventStore<BookEvent>();
// MySQL
builder.Services.AddMySqlEventStore<BookEvent>();By default, events are stored in a table called Events. You can specify a different name:
builder.Services.AddPostgresEventStore<BookEvent>(tableName: "BookEvents");Snapshot store
builder.Services.AddPostgresSnapshotStore<BookState>();Default table name is Snapshots. Override it the same way:
builder.Services.AddPostgresSnapshotStore<BookState>(tableName: "BookSnapshots");Checkpoint store
Each database adapter registers its own checkpoint store and lock provider:
builder.Services.AddPostgresCheckpointStore();
// or
builder.Services.AddSqlServerCheckpointStore();
// or
builder.Services.AddSqliteCheckpointStore();
// or
builder.Services.AddMySqlCheckpointStore();The PostgreSQL, SQL Server, and MySQL checkpoint stores also register a distributed lock provider (PostgresLockProvider, MsSqlLockProvider, or MySqlLockProvider). SQLite registers NoopLockProvider since it's single-process.
Aggregates
Full registration shorthand
Each adapter provides a method that registers the event store, snapshot store, and aggregate in one call:
builder.Services.AddPostgresAggregate<
BookAggregate, BookCommand, BookEvent, BookState>();This is equivalent to:
builder.Services.AddPostgresEventStore<BookEvent>();
builder.Services.AddPostgresSnapshotStore<BookState>();
builder.Services.AddAggregate<BookAggregate, BookCommand, BookEvent, BookState>();AggregateOptions
Control how aggregates load their event history:
| Property | Type | Default | Purpose |
|---|---|---|---|
InitializeBatchSize | int | 50 | Number of events to read per batch when loading an aggregate |
There's no explicit options registration. The batch size is a property on the aggregate options record. Configure it when you have aggregates with long event histories and want to tune the page size for loading.
Subscriptions
SubscriptionOptions
builder.Services.AddSubscription<BookProjection, BookEvent>(options =>
{
options.BatchSize = 25;
options.PollDelay = TimeSpan.FromMilliseconds(200);
options.MaxErrorDelay = TimeSpan.FromSeconds(60);
});| Property | Type | Default | Purpose |
|---|---|---|---|
BatchSize | int | 10 | Number of events to read per poll cycle |
PollDelay | TimeSpan | 100ms | Delay between polls when no new events are available |
MaxErrorDelay | TimeSpan | 30s | Maximum backoff delay when a subscription encounters an error |
BatchSize controls throughput. Higher values mean fewer round trips to the database but more events held in memory per batch. Start with the default and increase if your projection falls behind.
PollDelay controls latency. Lower values mean projections catch up faster after new events are written, at the cost of more frequent database queries. For most applications, 100ms is a good balance.
MaxErrorDelay is the upper bound for exponential backoff. If a subscription hits an error (database timeout, serialization failure), it retries with increasing delays up to this maximum.
Database-specific subscription registration
// Registers the subscription with the correct checkpoint store for the database
builder.Services.AddPostgresSubscription<BookProjection, BookEvent>();
builder.Services.AddSqlServerSubscription<BookProjection, BookEvent>();
builder.Services.AddSqliteSubscription<BookProjection, BookEvent>();Repository stores
Register read model repositories for dependency injection and health checks:
// Interface + implementation
builder.Services.AddRepositoryStore<IBookRepository, BookRepository>();
// Or just the implementation
builder.Services.AddRepositoryStore<BookRepository>();Both overloads also register the RepositoryStorageReadyHealthCheck.
Event upcasters
Register upcasters individually or as a chain:
// Individual registration (rebuilds the chain on each call)
builder.Services.AddEventUpcaster<BookAddedV1Upcaster>();
builder.Services.AddEventUpcaster<BookAddedV2Upcaster>();
// Or register the full chain at once
builder.Services.AddEventUpcasters(
new BookAddedV1Upcaster(),
new BookAddedV2Upcaster());See Event Versioning for details on writing upcasters.
Command middleware
builder.Services.AddCommandMiddleware<AuditMiddleware>();
builder.Services.AddCommandMiddleware<AuthorizationMiddleware>();Middleware executes in registration order. The first registered middleware is the outermost wrapper.
See Middleware for patterns and examples.
Messaging
Kafka transport
builder.Services.AddNagareKafkaTransport(builder.Configuration);This reads from the "Kafka" section of IConfiguration:
{
"Kafka": {
"BootstrapServers": "localhost:9092",
"Namespace": "my-app"
}
}| Property | Type | Required | Purpose |
|---|---|---|---|
BootstrapServers | string? | One of these | Standard Kafka bootstrap servers |
FullyQualifiedNamespace | string? | is required | Azure Event Hubs namespace (uses Azure Identity for auth) |
Namespace | string | Yes | Prefix for topic names and consumer groups |
Properties | Dictionary<string, string> | No | Additional Kafka client properties (SASL, SSL, etc.) |
For Azure Event Hubs, use FullyQualifiedNamespace instead of BootstrapServers:
{
"Kafka": {
"FullyQualifiedNamespace": "my-eventhub.servicebus.windows.net",
"Namespace": "my-app"
}
}MessageSubscriptionOptions
builder.Services.AddMessageSubscription<
InventoryBookHandler,
BookIntegrationEvent,
BookChannel>(options =>
{
options.GroupId = "inventory-service";
options.BatchSize = 100;
options.IdleDelay = TimeSpan.FromMilliseconds(500);
});| Property | Type | Default | Purpose |
|---|---|---|---|
GroupId | string? | null | Competing-consumer group identifier (maps to Kafka consumer group) |
BatchSize | int | 100 | Number of messages to process before acknowledging |
IdleDelay | TimeSpan | 500ms | Delay when the consumer has caught up and no new messages are available |
Message producer registration
builder.Services.AddMessageProducer<
BookMessageMapper, // IMessageMapper implementation
BookEvent, // Source event type (from event store)
BookChannel, // Channel definition
BookIntegrationEvent // Target message type (published to transport)
>();See Messaging for the full producer/consumer pattern.
Lock providers
For single-instance deployments, the default NoopLockProvider is fine. For multi-instance deployments, register a distributed lock provider:
// PostgreSQL (registered automatically by AddPostgresCheckpointStore)
services.AddSingleton<ILockProvider>(sp =>
new PostgresLockProvider(connectionString));
// SQL Server (registered automatically by AddSqlServerCheckpointStore)
services.AddSingleton<ILockProvider>(sp =>
new MsSqlLockProvider(connectionString));Lock providers prevent multiple instances of the same service from processing the same subscription events simultaneously.
JSON serialization
Nagare uses System.Text.Json with these defaults:
PropertyNameCaseInsensitive: trueDefaultIgnoreCondition: WhenWritingNullReferenceHandler: IgnoreCyclesJsonStringEnumConverterfor enum serialization
These settings are applied to event serialization and document store serialization. If you need custom converters, register them through NagareJsonSettings before adding your event stores.
Full example
A complete Program.cs for a service using PostgreSQL:
var builder = WebApplication.CreateBuilder(args);
// Database
builder.Services.AddNagarePostgresStorage(builder.Configuration);
builder.Services.AddPostgresCheckpointStore();
// Aggregate (includes event store + snapshot store)
builder.Services.AddPostgresAggregate<
BookAggregate, BookCommand, BookEvent, BookState>();
// Subscriptions
builder.Services.AddPostgresSubscription<BookProjection, BookEvent>();
builder.Services.AddSubscription<BookProjection, BookEvent>(options =>
{
options.BatchSize = 25;
options.PollDelay = TimeSpan.FromMilliseconds(50);
});
// Read models
builder.Services.AddRepositoryStore<IBookRepository, BookRepository>();
// Middleware
builder.Services.AddCommandMiddleware<CorrelationMiddleware>();
// Upcasters
builder.Services.AddEventUpcaster<BookAddedV1Upcaster>();
// Messaging (Kafka)
builder.Services.AddNagareKafkaTransport(builder.Configuration);
builder.Services.AddMessageProducer<
BookMessageMapper, BookEvent, BookChannel, BookIntegrationEvent>();
// Observability
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("Nagare")
.AddOtlpExporter());
var app = builder.Build();
app.MapHealthChecks("/health/ready");
app.MapGet("/books/{id}", async (string id, IBookRepository repo) =>
{
var book = await repo.GetById(id);
return book is not null ? Results.Ok(book) : Results.NotFound();
});
app.Run();Next: Store Adapters