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

«Когда использовать Event Sourcing?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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;
}

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