Когда использовать Event Sourcing?

Ответ

Event Sourcing — это архитектурный паттерн, при котором состояние приложения определяется последовательностью событий. Его стоит применять в следующих сценариях:

  • Требуется полный аудит и трассируемость: Например, в финансовых системах, где критично знать не только текущий баланс, но и всю историю его изменений (кто, когда и почему изменил).
  • Необходима возможность "переиграть" события: Для отладки сложных бизнес-процессов, создания снимков состояния на произвольную дату или построения новых проекций данных.
  • Сложная предметная область с агрегатами: Когда бизнес-логика сосредоточена вокруг агрегатов, чье состояние меняется в результате явных событий (например, OrderPlaced, PaymentReceived, ItemShipped).

Практический пример на C#:

// Доменное событие
public record OrderCreatedEvent(Guid OrderId, string CustomerId, DateTime CreatedAt);
public record OrderStatusChangedEvent(Guid OrderId, string OldStatus, string NewStatus, DateTime ChangedAt);

// Агрегат, использующий Event Sourcing
public class OrderAggregate
{
    public Guid Id { get; private set; }
    public string Status { get; private set; } = "New";
    private readonly List<object> _pendingEvents = new();

    // Восстановление состояния из истории событий
    public void LoadFromHistory(IEnumerable<object> pastEvents)
    {
        foreach (var e in pastEvents)
        {
            Apply(e);
        }
    }

    // Публичный метод для изменения статуса
    public void ChangeStatus(string newStatus)
    {
        if (Status == newStatus) return;

        // Генерируем событие
        var evt = new OrderStatusChangedEvent(Id, Status, newStatus, DateTime.UtcNow);
        Apply(evt);
        _pendingEvents.Add(evt); // Сохраняем для последующей записи
    }

    // Применение события к состоянию агрегата
    private void Apply(object evt)
    {
        switch (evt)
        {
            case OrderCreatedEvent created:
                Id = created.OrderId;
                break;
            case OrderStatusChangedEvent statusChanged:
                Status = statusChanged.NewStatus;
                break;
        }
    }

    // Получение новых событий для сохранения
    public IEnumerable<object> GetPendingEvents() => _pendingEvents;
}

Основной компромисс: Паттерн добавляет сложность (хранение и обработка потока событий), но дает беспрецедентную гибкость для анализа и реконструкции данных.

Ответ 18+ 🔞

Слушай, а вот Event Sourcing — это вообще ёперный театр, если честно. Представь, что твоё приложение — это не просто скучная база данных с последней версией правды, а полноценный сериал, где каждое изменение — это серия. И ты можешь в любой момент пересмотреть все серии с начала и понять, как герой до такого состояния дошёл.

Зачем это надо? Ну, во-первых, когда нужен полный аудит, и доверия ебать ноль. Типа в банках или бухгалтерии. Там мало знать, что на счету сейчас миллион. Надо точно знать, кто, когда и по какому поводу этот миллион туда положил, а потом тысячу снял на шаурму. Чтобы потом не было: "ой, а откуда у меня дырка в балансе?". Всё по полочкам, вся история как на ладони.

Во-вторых, это возможность "переиграть" события. Это вообще волшебная фишка. Накосячил в логике? Запустил процесс, который всё сломал? Да похуй! Берёшь всю историю событий до момента ебанины, меняешь код, который их обрабатывает, и запускаешь всё заново — как будто ничего и не было. Или тебе надо посмотреть, а как выглядела система ровно 1 января? Без проблем, накатываешь события только до этой даты — и вот тебе снимок. Удивление пиздец, какая мощь.

Ну и в-третьих, это сложная предметная область с агрегатами. Когда у тебя есть какая-то сущность, вокруг которой весь пиздец вертится — типа Заказа. И её состояние меняется не просто так, а в результате конкретных событий: OrderPlaced (заказ создали), PaymentReceived (деньги пришли), ItemShipped (товар уехал). Так вот Event Sourcing — это естественный способ это всё описать. Состояние агрегата — это просто результат применения всех событий к нему по порядку.

Смотри, как это примерно выглядит на C#. Ничего не трогай в коде, он правильный.

// Доменное событие
public record OrderCreatedEvent(Guid OrderId, string CustomerId, DateTime CreatedAt);
public record OrderStatusChangedEvent(Guid OrderId, string OldStatus, string NewStatus, DateTime ChangedAt);

// Агрегат, использующий Event Sourcing
public class OrderAggregate
{
    public Guid Id { get; private set; }
    public string Status { get; private set; } = "New";
    private readonly List<object> _pendingEvents = new();

    // Восстановление состояния из истории событий
    public void LoadFromHistory(IEnumerable<object> pastEvents)
    {
        foreach (var e in pastEvents)
        {
            Apply(e);
        }
    }

    // Публичный метод для изменения статуса
    public void ChangeStatus(string newStatus)
    {
        if (Status == newStatus) return;

        // Генерируем событие
        var evt = new OrderStatusChangedEvent(Id, Status, newStatus, DateTime.UtcNow);
        Apply(evt);
        _pendingEvents.Add(evt); // Сохраняем для последующей записи
    }

    // Применение события к состоянию агрегата
    private void Apply(object evt)
    {
        switch (evt)
        {
            case OrderCreatedEvent created:
                Id = created.OrderId;
                break;
            case OrderStatusChangedEvent statusChanged:
                Status = statusChanged.NewStatus;
                break;
        }
    }

    // Получение новых событий для сохранения
    public IEnumerable<object> GetPendingEvents() => _pendingEvents;
}

Видишь? Агрегат (OrderAggregate) хранит у себя текущий статус, но главное — он умеет восстанавливаться из кучи прошлых событий через LoadFromHistory. А когда ты меняешь статус, ты не просто присваиваешь поле. Ты сначала создаёшь событие OrderStatusChangedEvent, потом применяешь его к себе (метод Apply), и это событие летит в список _pendingEvents, чтобы его потом в базу сохранить. Вся магия — в этом методе Apply. Это как инструкция для агрегата: "чувак, случилось вот это — изменись соответственно".

А теперь про компромисс, ёпта. За всё хорошее надо платить. Event Sourcing — не серебряная пуля. Он добавляет овердохуища сложности. Вместо простого "обнови запись в таблице" тебе теперь надо: правильно спроектировать события, гарантировать их неизменяемость, хранить их все до одного, уметь быстро читать и применять длинную-предлинную историю. Это не для простых CRUD-приложений, где "положил-достал-удалил". Это тяжёлая артиллерия.

Но если тебе реально нужна эта сверхспособность — видеть всё, что было, и перематывать время — то другого пути нет. Это как выбрать между велосипедом и танком. На велосипеде по городу быстрее, но если тебе надо стену проломить и по кочкам проехать — тут только танк, бля буду. Выбирай с умом, а то получится мартышлюшка с микроскопом.