Codebelt

Savvyio.Domain.EventSourcing

Build event-sourced aggregates that replay traced domain events and maintain aggregate versions.

.NET 10.0 / .NET 9.0 MIT v5.0.8 11,209 downloads

Overview

Savvyio.Domain.EventSourcing adds an event-sourced aggregate model on top of Savvyio.Domain. It gives you a base aggregate that raises traced domain events, routes them through registered state handlers, and keeps aggregate version metadata alongside each event.

The package stays at the domain-model layer. It defines the traced aggregate and traced event primitives used to replay prior event streams and rebuild aggregate state, while storage, repositories, and DI integration are supplied by sibling extension packages.

Key APIs

TracedAggregateRoot<TKey> is the abstract base for event-sourced aggregates. New instances initialize handler dispatch from the registered delegates, while the rehydration constructor accepts an identifier and an ordered event stream, replays each event, and restores the aggregate version from event metadata.

TracedAggregateRoot<TKey>.RegisterDelegates(IFireForgetRegistry<ITracedDomainEvent>) is the required hook every derived aggregate implements. Register one handler per traced event type so replayed events and newly raised events mutate aggregate state through the same code path.

TracedAggregateRoot<TKey>.Version exposes the current event-stream version for the aggregate. It advances when new traced events are added and is restored during replay from the version stored on each traced event.

TracedDomainEvent is the base record for events stored in a traced aggregate stream. It builds on DomainEvent, including the event id and timestamp metadata initialization described in the source remarks.

TracedDomainEventExtensions.SetAggregateVersion<T> writes the aggregate version into event metadata and returns the same event instance. TracedAggregateRoot<TKey> uses this to stamp new events before they are stored.

TracedDomainEventExtensions.GetAggregateVersion<T> reads aggregate version metadata back from a traced event. Replay and persistence code can use that value to preserve event ordering and restore version state.

TracedDomainEventExtensions.GetMemberType<T> reads the stored member-type metadata for a traced event. This is useful when an event stream needs to identify the concrete CLR type that produced a serialized payload.

Basic usage

using Codebelt.Extensions.Xunit;
using Savvyio.Domain.EventSourcing;
using Xunit;

namespace MyProject.Tests;

public class TracedDomainEventMetadataExample : Test
{
    public TracedDomainEventMetadataExample(ITestOutputHelper output) : base(output) { }

    [Fact]
    public void StampMetadata_TracedDomainEvent_PreservesAggregateVersion()
    {
        var domainEvent = new AccountEmailAddressChanged("root@gimlichael.dev")
            .SetAggregateVersion(42);

        TestOutput.WriteLine($"AggregateVersion: {domainEvent.GetAggregateVersion()}");
        TestOutput.WriteLine($"MemberType: {domainEvent.GetMemberType()}");

        Assert.Equal(42, domainEvent.GetAggregateVersion());
        Assert.Contains(nameof(AccountEmailAddressChanged), domainEvent.GetMemberType());
    }
}

public sealed record AccountEmailAddressChanged(string EmailAddress) : TracedDomainEvent;

Use this pattern when your event stream needs traced domain events that carry aggregate version metadata before persistence or replay. It matters because the event instance keeps both ordering information and CLR type metadata in its metadata, which downstream replay or storage code can read directly.

Installation

dotnet add package Savvyio.Domain.EventSourcing

Usage guidance

Adopt this package when your domain model needs aggregate-local event replay, aggregate version tracking, and traced domain events that can later be serialized or rehydrated by infrastructure packages. If you only need plain domain entities and in-process domain events, Savvyio.Domain is the smaller starting point, and if you also need repositories or event persistence you should add one of the sibling EF Core or dependency injection extension packages instead of expecting those concerns from this package.

Family packages