Документация

Koto.EventSourcing.Marten

Event Sourcing на PostgreSQL через Marten.

Установка

dotnet add package Koto.EventSourcing.Marten

Что внутри

ТипНазначение
EventSourcedAggregateRoot<TId>Базовый класс с RaiseEvent, Apply и Reconstitute
IEventSourcedRepository<TAgg, TId>Контракт персистентности для event-sourced агрегатов
MartenEventSourcedRepository<TAgg, TId>Реализация на Marten
InlineProjection<TReadModel, TId>База для инлайновых single-stream проекций
AsyncProjection<TReadModel, TId>База для асинхронных daemon single-stream проекций
AddKotoMarten()Регистрирует Marten и generic-репозиторий

Использование

1 — Event-sourced агрегат

public sealed class Order : EventSourcedAggregateRoot<OrderId>
{
    public decimal Total { get; private set; }
    public bool IsShipped { get; private set; }

    public static Order Place(OrderId id, decimal total)
    {
        var order = new Order();
        order.RaiseEvent(new OrderPlaced(Guid.NewGuid(), DateTime.UtcNow, id, total));
        return order;
    }

    public void Ship() => RaiseEvent(new OrderShipped(Guid.NewGuid(), DateTime.UtcNow));

    protected override void Apply(IDomainEvent @event)
    {
        switch (@event)
        {
            case OrderPlaced e: Total = e.Total; break;
            case OrderShipped:  IsShipped = true; break;
        }
    }

    private Order() { }
}

2 — Регистрация

builder.Services.AddKotoMarten(
    builder.Configuration.GetConnectionString("Db")!,
    opts =>
    {
        // опциональная дополнительная настройка Marten
        opts.Projections.Add<OrderSummaryProjection>(ProjectionLifecycle.Async);
    });

3 — Репозиторий

public class PlaceOrderHandler : ICommandHandler<PlaceOrderCommand, Result<OrderId>>
{
    private readonly IEventSourcedRepository<Order, OrderId> _orders;

    public async Task<Result<OrderId>> Handle(PlaceOrderCommand cmd, CancellationToken ct)
    {
        var order = Order.Place(OrderId.New(), cmd.Total);
        await _orders.SaveAsync(order, ct);
        return order.Id;
    }
}

4 — Проекция

public sealed class OrderSummaryProjection : AsyncProjection<OrderSummary, Guid>
{
    public OrderSummary Create(OrderPlaced e) => new(e.OrderId, e.Total, false);
    public OrderSummary Apply(OrderShipped _, OrderSummary current) =>
        current with { IsShipped = true };
}

Marten вместе с EF Core

Marten и Koto.Infrastructure.EFCore могут делить один экземпляр PostgreSQL. Marten управляет потоками событий (mt_events, mt_streams); EF Core — read-моделями и остальными сущностями.