Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Eventuous/eventuous/llms.txt

Use this file to discover all available pages before exploring further.

Eventuous integrates with the standard Microsoft.Extensions.DependencyInjection container through extension methods that register stores, command services, subscriptions, and aggregate factories as singletons. All registrations are tracing-aware: when EventuousDiagnostics.Enabled is true (the default), each registration automatically wraps the concrete type with its instrumented counterpart so distributed traces are captured without any extra setup. Install the NuGet package to get started:
Eventuous.Extensions.DependencyInjection

Event store

AddEventStore<T>() registers the event store as IEventStore, IEventReader, and IEventWriter. When diagnostics are enabled, the concrete instance is wrapped in TracedEventStore and all three interfaces resolve the traced wrapper:
services.AddEventStore<KurrentDBEventStore>();
Use the factory overload when construction requires runtime configuration:
services.AddEventStore<KurrentDBEventStore>(
    sp => new KurrentDBEventStore(sp.GetRequiredService<KurrentDBClient>())
);
If you only need separate read or write capabilities without a full store, use the more granular helpers:
MethodRegistered interface
AddEventReader<T>()IEventReader
AddEventWriter<T>()IEventWriter
AddEventReaderWriter<T>()IEventReader and IEventWriter
All four methods have matching factory overloads that accept Func<IServiceProvider, T>.

Command service

AddCommandService<TService, TState>() registers the concrete service as a singleton and exposes it as ICommandService<TState>. When diagnostics are enabled, the ICommandService<TState> binding resolves a TracedCommandService<TState> wrapper instead of the concrete type directly:
services.AddCommandService<BookingsCommandService, BookingState>();
Both TService and ICommandService<TState> are registered, so you can inject either type. Use the factory overload when the service has non-trivial construction:
services.AddCommandService<BookingsCommandService, BookingState>(
    sp => new BookingsCommandService(
        sp.GetRequiredService<IEventStore>(),
        sp.GetRequiredService<Services.IsRoomAvailable>()
    )
);

Aggregate factory

By default Eventuous instantiates aggregates with new T(), which requires a public parameterless constructor. If your aggregate has constructor-injected dependencies, register a custom factory:
// Custom factory lambda
services.AddAggregate<Booking, BookingState>(
    sp => new Booking(sp.GetRequiredService<IBookingPolicy>())
);

// Or let the container resolve it automatically (adds AddTransient<T>())
services.AddAggregate<Booking, BookingState>();
Both variants register a ResolveAggregateFactory entry and ensure AggregateFactoryRegistry is registered as a singleton in the DI container. The registry’s DI-aware constructor accepts IEnumerable<ResolveAggregateFactory> and wires up all custom factories automatically when the container first resolves it — no separate activation step is required.
Do not call AddAggregate if your aggregate has a public parameterless constructor and no constructor dependencies — the default factory handles that case automatically without any registration.

Subscriptions

Subscriptions are long-running background services. AddSubscription<TSubscription, TOptions>() registers both the subscription singleton and a SubscriptionHostedService (an IHostedService) so the subscription starts and stops with the application automatically:
services.AddSubscription<AllStreamSubscription, AllStreamSubscriptionOptions>(
    "BookingsProjections",
    builder => builder
        .UseCheckpointStore<MongoCheckpointStore>()
        .AddEventHandler<BookingStateProjection>()
        .AddEventHandler<MyBookingsProjection>()
        .WithPartitioningByStream(2)
);
The SubscriptionBuilder methods available inside the configuration delegate:
MethodPurpose
AddEventHandler<T>()Register an event handler (resolved from DI, keyed by subscription ID)
AddEventHandler<T>(Func<IServiceProvider, T>)Register a handler with a custom factory
UseCheckpointStore<T>()Use a subscription-specific checkpoint store instead of the global one
UseCheckpointStore<T>(Func<IServiceProvider, T>)Factory overload for the checkpoint store
WithPartitioningByStream(int)Partition event consumption by stream name across N partitions
WithPartitioning(int, GetPartitionKey)Partition with a custom key function
Configure(Action<TOptions>)Configure subscription-specific options (e.g., stream name)
UseSerializer<T>()Override the event serializer for this subscription
A persistent subscription that needs custom configuration (e.g., a specific stream name) uses Configure:
services.AddSubscription<StreamPersistentSubscription, StreamPersistentSubscriptionOptions>(
    "PaymentIntegration",
    builder => builder
        .Configure(x => x.StreamName = PaymentsIntegrationHandler.Stream)
        .AddEventHandler<PaymentsIntegrationHandler>()
);

Logging bridge

Eventuous emits internal diagnostics through .NET EventSource. LoggingEventListener subscribes to those sources and forwards events to ILoggerFactory, making internal Eventuous activity visible in your application logs. Instantiate it after building the app and dispose it on shutdown:
var factory  = app.Services.GetRequiredService<ILoggerFactory>();
var listener = new LoggingEventListener(factory);

try {
    app.Run();
} finally {
    listener.Dispose();
}
Pass "OpenTelemetry" as the second argument to also capture diagnostics emitted by the OpenTelemetry SDK itself (whose EventSource names start with "OpenTelemetry"):
var listener = new LoggingEventListener(factory, "OpenTelemetry");

Complete registration example

The following excerpt is taken directly from the Bookings sample and shows a realistic AddEventuous extension method that wires together all of the above:
public static class Registrations {
    extension(IServiceCollection services) {
        public void AddEventuous(IConfiguration configuration) {
            // Event store (wraps KurrentDB client)
            services.AddKurrentDBClient(configuration["KurrentDB:ConnectionString"]!);
            services.AddEventStore<KurrentDBEventStore>();

            // Command service
            services.AddCommandService<BookingsCommandService, BookingState>();

            // Application-layer delegates (infrastructure services)
            services.AddSingleton<Services.IsRoomAvailable>((_, _) => new(true));
            services.AddSingleton<Services.ConvertCurrency>(
                (from, currency) => new(from.Amount * 2, currency)
            );

            // Read-model infrastructure
            services.AddSingleton(Mongo.ConfigureMongo(configuration));

            // All-stream subscription for projections
            services.AddSubscription<AllStreamSubscription, AllStreamSubscriptionOptions>(
                "BookingsProjections",
                builder => builder
                    .UseCheckpointStore<MongoCheckpointStore>()
                    .AddEventHandler<BookingStateProjection>()
                    .AddEventHandler<MyBookingsProjection>()
                    .WithPartitioningByStream(2)
            );
            services.AddSingleton<BookingsQueryService>();

            // Persistent subscription for payment integration
            services.AddSubscription<StreamPersistentSubscription, StreamPersistentSubscriptionOptions>(
                "PaymentIntegration",
                builder => builder
                    .Configure(x => x.StreamName = PaymentsIntegrationHandler.Stream)
                    .AddEventHandler<PaymentsIntegrationHandler>()
            );
        }
    }
}

Build docs developers (and LLMs) love