Ответ
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-приложений, где "положил-достал-удалил". Это тяжёлая артиллерия.
Но если тебе реально нужна эта сверхспособность — видеть всё, что было, и перематывать время — то другого пути нет. Это как выбрать между велосипедом и танком. На велосипеде по городу быстрее, но если тебе надо стену проломить и по кочкам проехать — тут только танк, бля буду. Выбирай с умом, а то получится мартышлюшка с микроскопом.