Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/events/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ Polecat converts .NET event type names to snake_case for storage:
- `MembersJoined` → `members_joined`
- `InvoiceLineItemAdded` → `invoice_line_item_added`

## Pre-registering Event Types

Event types are registered with the store on the fly the first time they are appended, so registering
them up front is optional. It is worth doing when the *reading* side may meet an event type before the
writing side does -- most commonly the async daemon in a separate process, which has to map an
`event_type_name` back to a .NET type it has not yet seen appended:

```cs
var store = DocumentStore.For(opts =>
{
opts.Connection(connectionString);

opts.Events.AddEventType<QuestStarted>();
opts.Events.AddEventType(typeof(MembersJoined));
opts.Events.AddEventTypes([typeof(MembersDeparted), typeof(QuestEnded)]);
});
```

This mirrors Marten's `StoreOptions.Events.AddEventType<T>()` / `AddEventTypes(...)`.

## Sequence IDs

Events are assigned a global, monotonically increasing sequence ID via SQL Server's `IDENTITY(1,1)`. This provides a total ordering of all events across all streams, which is critical for the async daemon's event processing.
Expand Down
4 changes: 1 addition & 3 deletions src/Polecat.Tests/Compliance/PolecatComplianceFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,7 @@ public PolecatComplianceRegistrar(StoreOptions options)
_options = options;
}

// Straight to the registry: unlike Marten, Polecat's public EventStoreOptions facade has no
// AddEventType, so the shared IEventRegistry member on StoreOptions.EventGraph is the seam.
public void AddEventType(Type eventType) => _options.EventGraph.AddEventType(eventType);
public void AddEventType(Type eventType) => _options.Events.AddEventType(eventType);

public ITagTypeRegistration RegisterTagType<TTag>(string tableSuffix) where TTag : notnull
=> _options.Events.RegisterTagType<TTag>(tableSuffix);
Expand Down
47 changes: 47 additions & 0 deletions src/Polecat.Tests/StoreOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,51 @@ public void can_configure_event_store_options()
options.Events.EnableCausationId.ShouldBeTrue();
options.Events.EnableHeaders.ShouldBeTrue();
}

[Fact]
public void add_event_type_generic_registers_the_event_type()
{
var options = new StoreOptions();
options.Events.AddEventType<StoreOptionsEventTypeA>();

options.EventGraph.AllKnownEventTypes()
.ShouldContain(x => x.EventType == typeof(StoreOptionsEventTypeA));
}

[Fact]
public void add_event_type_by_type_registers_the_event_type()
{
var options = new StoreOptions();
options.Events.AddEventType(typeof(StoreOptionsEventTypeB));

options.EventGraph.AllKnownEventTypes()
.ShouldContain(x => x.EventType == typeof(StoreOptionsEventTypeB));
}

[Fact]
public void add_event_types_registers_all_of_them()
{
var options = new StoreOptions();
options.Events.AddEventTypes([typeof(StoreOptionsEventTypeA), typeof(StoreOptionsEventTypeB)]);

var known = options.EventGraph.AllKnownEventTypes();
known.ShouldContain(x => x.EventType == typeof(StoreOptionsEventTypeA));
known.ShouldContain(x => x.EventType == typeof(StoreOptionsEventTypeB));
}

[Fact]
public void add_event_type_is_idempotent()
{
var options = new StoreOptions();
options.Events.AddEventType<StoreOptionsEventTypeA>();
options.Events.AddEventType<StoreOptionsEventTypeA>();

options.EventGraph.AllKnownEventTypes()
.Count(x => x.EventType == typeof(StoreOptionsEventTypeA))
.ShouldBe(1);
}
}

public record StoreOptionsEventTypeA(string Name);

public record StoreOptionsEventTypeB(int Count);
30 changes: 30 additions & 0 deletions src/Polecat/StoreOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,36 @@ bool IEventStoreInstrumentation.ExtendedProgressionEnabled
public Polecat.Events.Aggregation.IMessageOutbox MessageOutbox { get; set; }
= Polecat.Events.Aggregation.NulloMessageOutbox.Instance;

/// <summary>
/// Pre-register an event type with the event store. Mirrors Marten's
/// <c>StoreOptions.Events.AddEventType&lt;TEvent&gt;()</c>. Not strictly necessary — event types are
/// registered on the fly as they are appended — but pre-registration can help with asynchronous
/// projections where the daemon process hasn't yet encountered the event type, and lets the
/// event type name alias be resolved before the first append.
/// </summary>
public void AddEventType<TEvent>() where TEvent : notnull
{
EventGraph!.AddEventType(typeof(TEvent));
}

/// <summary>
/// Pre-register an event type with the event store. Mirrors Marten's
/// <c>StoreOptions.Events.AddEventType(Type)</c>.
/// </summary>
public void AddEventType(Type eventType)
{
EventGraph!.AddEventType(eventType);
}

/// <summary>
/// Pre-register several event types with the event store in one call. Mirrors Marten's
/// <c>StoreOptions.Events.AddEventTypes(IEnumerable&lt;Type&gt;)</c>.
/// </summary>
public void AddEventTypes(IEnumerable<Type> eventTypes)
{
foreach (var eventType in eventTypes) EventGraph!.AddEventType(eventType);
}

/// <summary>
/// Register a tag type for Dynamic Consistency Boundary (DCB) support.
/// Creates a tag table with an auto-generated suffix.
Expand Down
Loading