What's new in 0.16
Eventuous v0.16 is a major release with new infrastructure support, source generators, performance improvements, and important bug fixes. Some breaking changes require attention when upgrading from 0.15.x.
Breaking changes
Section titled “Breaking changes”.NET SDK targets
Section titled “.NET SDK targets”Eventuous 0.16 drops support for .NET 6 and .NET 7. The library now targets .NET 8, .NET 9, and .NET 10.
KurrentDB migration
Section titled “KurrentDB migration”EventStoreDB packages have been renamed to KurrentDB, reflecting the product rebrand.
| Before (0.15) | After (0.16) |
|---|---|
Package Eventuous.EventStore | Eventuous.KurrentDB |
Namespace Eventuous.EventStore | Eventuous.KurrentDB |
Class EsdbEventStore | KurrentDBEventStore |
AddEventStoreClient(...) | AddKurrentDBClient(...) |
EventStoreClient | KurrentDBClient |
EventStoreClientSettings | KurrentDBClientSettings |
The old class name EsdbEventStore is available as an obsolete type alias for a transitional period. Package references and using statements must be updated.
StreamEvent property rename
Section titled “StreamEvent property rename”StreamEvent.Position has been renamed to StreamEvent.Revision for clarity. Update all code that accesses this property:
// Beforevar position = streamEvent.Position;
// Aftervar revision = streamEvent.Revision;Tracing metadata field names
Section titled “Tracing metadata field names”Metadata field names used for distributed tracing have changed, and the parent span id has been removed:
| Before | After |
|---|---|
trace-id | $traceId |
span-id | $spanId |
parent-span-id | (removed) |
The TracingMeta record no longer has the ParentSpanId property. If you read tracing metadata from event metadata directly, update the field names. If you only use the Eventuous diagnostics API, no changes are needed beyond recompilation.
Spyglass API
Section titled “Spyglass API”Spyglass now uses source-generated module initializers to populate its registry automatically. The AddEventuousSpyglass() DI registration call is now a no-op (marked obsolete). The ping endpoint (/spyglass/ping) now returns "v1.0". Previous versions of the Spyglass UI are not compatible with this version of the API.
New features
Section titled “New features”SQLite event store
Section titled “SQLite event store”A new Eventuous.Sqlite package provides a full SQLite-based event store for embedded and local applications (desktop, mobile, CLI tools) that need event sourcing without external database dependencies.
It includes an event store, subscriptions (all-stream and per-stream), checkpoint store, projector base class, and automatic schema initialization. WAL mode is enabled by default.
builder.Services.AddEventuousSqlite( "Data Source=myapp.db", schema: "eventuous", initializeDatabase: true);builder.Services.AddEventStore<SqliteStore>();See the SQLite infrastructure documentation for full details.
Azure Service Bus
Section titled “Azure Service Bus”A new Eventuous.Azure.ServiceBus package adds Azure Service Bus as a producer and subscription target.
Producer publishes messages to queues or topics:
builder.Services.AddSingleton(new ServiceBusClient(connectionString));builder.Services.AddProducer<ServiceBusProducer, ServiceBusProducerOptions>( options => options.QueueOrTopicName = "my-topic");Subscription consumes messages from queues or topics, with support for session-based processing:
builder.Services.AddSubscription<ServiceBusSubscription, ServiceBusSubscriptionOptions>( "PaymentsIntegration", b => b .Configure(cfg => cfg.QueueOrTopic = new Topic("payments")) .AddEventHandler<PaymentsHandler>());For session-ordered message processing, configure SessionProcessorOptions on the subscription options.
Source generators
Section titled “Source generators”Eventuous 0.16 includes three new compile-time source generators that eliminate runtime reflection and improve AOT compatibility.
Type mapping generator
Section titled “Type mapping generator”The [EventType] attribute is now processed at compile time. A module initializer is generated that automatically registers all decorated event types in the TypeMap:
[EventType("BookingImported")]public record BookingImported(string RoomId, LocalDate CheckIn, LocalDate CheckOut, decimal Price);No manual TypeMap.RegisterKnownEventTypes() call is needed when using the source generator. The generated code runs at module initialization before any application code.
Consume context converter generator
Section titled “Consume context converter generator”Event handlers that use IMessageConsumeContext<T> previously relied on runtime-compiled expressions for type conversion. A source generator now produces these converters at compile time, eliminating reflection overhead.
Event usage analyzer
Section titled “Event usage analyzer”A new diagnostic analyzer (EVTC001) warns at compile time when event types used in Apply<TEvent>(), When<TEvent>(), or On<TEvent>() handler registrations are missing the [EventType] attribute. This catches missing type map registrations before runtime. If the type is registered explicitly (not via attribute), the warning is suppressed.
HTTP command analyzer
Section titled “HTTP command analyzer”A new analyzer detects route mismatches and state type mismatches in HTTP command mappings. A companion generator can map HTTP commands and emits warnings on duplicate routes.
Checkpoint initial position
Section titled “Checkpoint initial position”Subscriptions with checkpoints now support configuring the initial position when no checkpoint exists. Previously, subscriptions always started from the beginning. You can now choose:
builder.Services.AddSubscription<MySubscription, MySubscriptionOptions>( "MySubscription", b => b .Configure(cfg => cfg.StartFrom = InitialPosition.Latest) .AddEventHandler<MyHandler>());InitialPosition.Earliest(default) — start from the beginning of the streamInitialPosition.Latest— start from the current end of the stream, only processing new events
Gap handling for PostgreSQL global subscriptions
Section titled “Gap handling for PostgreSQL global subscriptions”PostgreSQL global subscriptions now detect and handle gaps in event sequences. When a gap is detected (due to uncommitted transactions or deleted events), the subscription can insert tombstone events to bridge the gap and continue processing. This prevents subscriptions from stalling on missing sequence numbers.
Performance optimizations
Section titled “Performance optimizations”Several internal optimizations reduce memory allocations in the subscription pipeline:
- HandlingResults: uses a single-field optimization for the common single-handler case, avoiding list allocation (158x faster for single results)
- ContextItems: lazy-initialized backing dictionary, saving 104 bytes per message when no context items are used
- Manual iteration: replaced LINQ operations with array indexing in hot paths to avoid allocator pressure
Google PubSub automatic reconnection
Section titled “Google PubSub automatic reconnection”Google PubSub subscriptions now automatically detect unrecoverable subscriber failures and trigger reconnection, matching the behavior of other subscription implementations.
Custom serializer for subscriptions
Section titled “Custom serializer for subscriptions”All subscriptions now accept an optional IEventSerializer parameter, allowing you to use a different serializer per subscription instead of the global default.
MongoDB custom collection names
Section titled “MongoDB custom collection names”MongoDB projections and checkpoint store now support custom collection names via MongoProjectionOptions<T>:
builder.Services.AddSubscription<..., ...>( "MyProjection", b => b .AddEventHandler(sp => new MyProjector(sp, "custom_collection_name")));The default collection name is derived from the document type with automatic suffix removal (Document, Entity, View, Projection).
Command service improvements
Section titled “Command service improvements”- Override events and expected version: command services now support amending the proposed append before persistence, allowing you to modify events or override the expected version. This is useful for advanced scenarios like multitenant event routing.
- Functional service bug fix: fixed a bug where
ProposedEventwas passed toState.When()instead of the raw event data, preventing correct state updates when usingICommandService<TState>. - Store resolution by command: command services can resolve the store based on command payload, useful for multitenant environments.
Bug fixes
Section titled “Bug fixes”- Checkpoint subscription resubscribe state reset (#509): fixed resubscription after handler failure skipping the failed event. When
ThrowOnErroris enabled and a handler throws, the subscription now resets its in-memory checkpoint state (LastProcessed,Sequence, andCheckpointCommitHandler) before resubscribing. Previously,GetCheckpoint()would return the staleLastProcessedvalue (which may have advanced past the failed event), causing the resubscribed run to skip it. The checkpoint commit handler is also disposed and recreated to avoid sequence gaps. - RabbitMQ queue binding (#498): fixed
QueueBindusingSubscriptionIdinstead of the resolved queue name whenQueueOptions.Queuewas overridden. Also fixedGateway.GetOriginalStream()returning null due to incorrect type casting. - Aggregate version calculation (#475): fixed version tracking for deleted streams by storing
OriginalVersionas a field instead of computing it. - PostgreSQL read_stream_backwards (#464): fixed guard clause that incorrectly mixed page size with position validation, causing valid backwards reads to return empty results.
- AddCompositionEventHandler DI registration (#462): fixed factory function registration — the factory was being registered as a value instead of being invoked during resolution.
- Service Bus message attributes (#460): non-serializable custom attributes are now filtered out before sending to Azure Service Bus, preventing serialization errors.
- Type map analyzer (#458): suppresses warnings when types are registered explicitly rather than via the
[EventType]attribute. - SQL Server improvements (#431): multiple stored procedure improvements, fixes for issue #428 (optimistic concurrency reporting).
- SQL Server WrongExpectedVersion (#376): stored procedures now properly report optimistic concurrency exceptions.
- ActionResult creation (#378): fixed incorrect
ActionResultconstruction in HTTP command API. - Stream not found log (#403): stores no longer log a warning when reading a non-existent stream if the caller expects it (e.g., when using
ExpectedVersion.Any). - Global position in FromSuccess (#386): corrected position type used in success result.
- Connection string from options (#417): PostgreSQL connection string is now properly fetched from connection options.
Infrastructure updates
Section titled “Infrastructure updates”- KurrentDB client updated to 1.3.0
- MongoDB driver updated
- .NET 10 GA support (previously preview)
- TUnit test framework upgraded