Generated Code¶
This page covers the plain .NET interfaces, implementations, and DI registration generated by the Event Store module — no effects or Eff<RT, T> required.
Generated Store Interface¶
For each [EventSourcedAggregate], the generator produces a store interface:
public interface IOrderEventStore : IAutoCommittable
{
// Fetch for writing with optimistic concurrency
Task<IEventStream<Order>> FetchForWritingAsync(OrderId id, CancellationToken ct = default);
// Rebuild aggregate from events
Task<Order?> AggregateAsync(OrderId id, CancellationToken ct = default);
// Rebuild aggregate up to a specific version
Task<Order?> AggregateAsync(OrderId id, long version, CancellationToken ct = default);
// Start a new stream with an explicit identity
Task StartStreamAsync(OrderId id, IReadOnlyList<object> events, CancellationToken ct = default);
// Start a new stream with an auto-generated identity
Task<OrderId> StartStreamAsync(IReadOnlyList<object> events, CancellationToken ct = default);
// Append events to an existing stream
Task AppendAsync(OrderId id, IReadOnlyList<object> events, CancellationToken ct = default);
// Append with optimistic concurrency check
Task AppendOptimisticAsync(OrderId id, long expectedVersion,
IReadOnlyList<object> events, CancellationToken ct = default);
// Fetch all events from a stream
Task<IReadOnlyList<EventEnvelope>> FetchStreamAsync(OrderId id, CancellationToken ct = default);
}
All store interfaces extend IAutoCommittable, enabling automatic commit after command dispatch when used with the Dispatch module.
IEventStream<T>¶
Returned by FetchForWritingAsync — holds the current aggregate state and buffers appended events:
public interface IEventStream<out TAggregate>
{
TAggregate? Aggregate { get; } // Current state, or null if not found
long CurrentVersion { get; } // For optimistic concurrency
void AppendOne(object @event); // Buffer a single event
void AppendMany(IEnumerable<object> events); // Buffer multiple events
}
Events are not persisted until CommitAsync() is called. For fluent event appending in Eff pipelines, see Stream Extensions.
EventEnvelope¶
Events are stored wrapped in an envelope with metadata:
| Field | Type | Description |
|---|---|---|
Data |
object |
The event payload |
EventId |
Guid |
Unique event occurrence identifier |
Version |
long |
Stream version at which this event was appended |
Timestamp |
DateTimeOffset |
When the event was persisted |
EventTypeName |
string |
CLR type name of the event |
CorrelationId |
string? |
From CorrelationContext |
CausationId |
string? |
From CorrelationContext |
UserId |
string? |
From CorrelationContext |
TenantId |
string? |
From CorrelationContext |
Metadata |
IReadOnlyDictionary<string, string>? |
Custom metadata |
Metadata fields are automatically populated from CorrelationContext.Current when events are committed.
In-Memory Implementation¶
A ConcurrentDictionary-backed implementation is generated for every aggregate:
Key behaviors:
- Events are buffered in a
_pendinglist untilCommitAsync()is called CommitAsync()wraps each event in anEventEnvelopewith metadata fromCorrelationContext.CurrentAggregateAsync()rebuilds the aggregate viaAggregateFold<T>.Fold()(Create/Apply convention)- Optimistic concurrency checks stream version on
AppendOptimisticAsync()
Registered by default via TryAddSingleton — infrastructure implementations (e.g., Marten) override it.
DI Registration¶
The generator creates a registration extension:
public static class AppEventStoreRegistration
{
public static IServiceCollection AddAppEventStore(this IServiceCollection services)
{
services.TryAddSingleton<IOrderEventStore, InMemoryOrderEventStore>();
// ... per-aggregate registrations
return services;
}
}
TryAddSingleton means infrastructure implementations registered first take priority:
// In-memory (default)
services.AddAppEventStore();
// Marten overrides in-memory (registered as scoped, takes priority)
services.AddAppEventStoreMarten(connectionString);
services.AddAppEventStore(); // InMemoryOrderEventStore won't override
Direct Injection¶
Inject IOrderEventStore directly:
public class OrderService(IOrderEventStore orderStore)
{
public async Task<Order?> GetAsync(OrderId id) =>
await orderStore.AggregateAsync(id);
public async Task CreateAsync(OrderId id, string customerName)
{
await orderStore.StartStreamAsync(id, [new OrderCreated(id, customerName)]);
await orderStore.CommitAsync();
}
public async Task AddItemAsync(OrderId id, string sku, int quantity)
{
await orderStore.AppendAsync(id, [new OrderItemAdded(sku, quantity)]);
await orderStore.CommitAsync();
}
}
See Effects for the Eff<RT, T> composition layer, or Attributes for the full attribute reference.