Skip to content

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:

csharp
// 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:

json
{
  "ConnectionStrings": {
    "DbConnectionString": "Host=localhost;Database=myapp;Username=postgres;Password=secret"
  }
}

You can pass a different name:

csharp
builder.Services.AddNagarePostgresStorage(
    builder.Configuration,
    connectionStringName: "EventStoreDb");

Event store

Register an event store for each event type in your system:

csharp
// 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:

csharp
builder.Services.AddPostgresEventStore<BookEvent>(tableName: "BookEvents");

Snapshot store

csharp
builder.Services.AddPostgresSnapshotStore<BookState>();

Default table name is Snapshots. Override it the same way:

csharp
builder.Services.AddPostgresSnapshotStore<BookState>(tableName: "BookSnapshots");

Checkpoint store

Each database adapter registers its own checkpoint store and lock provider:

csharp
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:

csharp
builder.Services.AddPostgresAggregate<
    BookAggregate, BookCommand, BookEvent, BookState>();

This is equivalent to:

csharp
builder.Services.AddPostgresEventStore<BookEvent>();
builder.Services.AddPostgresSnapshotStore<BookState>();
builder.Services.AddAggregate<BookAggregate, BookCommand, BookEvent, BookState>();

AggregateOptions

Control how aggregates load their event history:

PropertyTypeDefaultPurpose
InitializeBatchSizeint50Number 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

csharp
builder.Services.AddSubscription<BookProjection, BookEvent>(options =>
{
    options.BatchSize = 25;
    options.PollDelay = TimeSpan.FromMilliseconds(200);
    options.MaxErrorDelay = TimeSpan.FromSeconds(60);
});
PropertyTypeDefaultPurpose
BatchSizeint10Number of events to read per poll cycle
PollDelayTimeSpan100msDelay between polls when no new events are available
MaxErrorDelayTimeSpan30sMaximum 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

csharp
// 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:

csharp
// 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:

csharp
// 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

csharp
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

csharp
builder.Services.AddNagareKafkaTransport(builder.Configuration);

This reads from the "Kafka" section of IConfiguration:

json
{
  "Kafka": {
    "BootstrapServers": "localhost:9092",
    "Namespace": "my-app"
  }
}
PropertyTypeRequiredPurpose
BootstrapServersstring?One of theseStandard Kafka bootstrap servers
FullyQualifiedNamespacestring?is requiredAzure Event Hubs namespace (uses Azure Identity for auth)
NamespacestringYesPrefix for topic names and consumer groups
PropertiesDictionary<string, string>NoAdditional Kafka client properties (SASL, SSL, etc.)

For Azure Event Hubs, use FullyQualifiedNamespace instead of BootstrapServers:

json
{
  "Kafka": {
    "FullyQualifiedNamespace": "my-eventhub.servicebus.windows.net",
    "Namespace": "my-app"
  }
}

MessageSubscriptionOptions

csharp
builder.Services.AddMessageSubscription<
    InventoryBookHandler,
    BookIntegrationEvent,
    BookChannel>(options =>
{
    options.GroupId = "inventory-service";
    options.BatchSize = 100;
    options.IdleDelay = TimeSpan.FromMilliseconds(500);
});
PropertyTypeDefaultPurpose
GroupIdstring?nullCompeting-consumer group identifier (maps to Kafka consumer group)
BatchSizeint100Number of messages to process before acknowledging
IdleDelayTimeSpan500msDelay when the consumer has caught up and no new messages are available

Message producer registration

csharp
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:

csharp
// 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: true
  • DefaultIgnoreCondition: WhenWritingNull
  • ReferenceHandler: IgnoreCycles
  • JsonStringEnumConverter for 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:

csharp
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

流れ — flow.