Ответ
Event Sourcing — это архитектурный паттерн, при котором все изменения состояния приложения сохраняются в виде последовательности неизменяемых событий. Текущее состояние системы не хранится напрямую, а вычисляется путем последовательного применения всех событий с самого начала.
Журнал событий становится единственным источником правды (Single Source of Truth).
Основная идея:
Вместо того чтобы выполнять UPDATE User SET status = 'active' WHERE id = 123
, мы сохраняем событие UserActivated { UserID: 123, Timestamp: ... }
.
Пример на Go:
// События
type AccountCreated struct {
ID string
Owner string
}
type MoneyDeposited struct {
Amount int
}
type MoneyWithdrawn struct {
Amount int
}
// Агрегат (сущность)
type Account struct {
ID string
Owner string
Balance int
}
// Восстановление состояния из истории событий
func NewAccountFromEvents(events []interface{}) *Account {
a := &Account{}
for _, event := range events {
a.Apply(event)
}
return a
}
// Применение одного события к состоянию
func (a *Account) Apply(event interface{}) {
switch e := event.(type) {
case AccountCreated:
a.ID = e.ID
a.Owner = e.Owner
case MoneyDeposited:
a.Balance += e.Amount
case MoneyWithdrawn:
a.Balance -= e.Amount
}
}
Преимущества:
- Полный аудиторский след: Всегда можно узнать, кто, что и когда изменил, так как вся история сохранена.
- Отладка и "путешествия во времени": Можно восстановить состояние системы на любой момент в прошлом, что упрощает отладку.
- Аналитика: Легко строить различные проекции данных (модели для чтения) на основе одного и того же журнала событий.
Недостатки:
- Сложность реализации: Паттерн сложнее для понимания и реализации, чем традиционное CRUD-хранение.
- Версионирование событий: Если структура события меняется, нужно поддерживать обратную совместимость для корректного воспроизведения старых событий.
- Производительность восстановления: Восстановление состояния для сущностей с очень длинной историей может быть медленным (решается с помощью снимков состояния — snapshots).
Связь с CQRS:
Event Sourcing часто используется вместе с паттерном CQRS (Command Query Responsibility Segregation), где модели для записи (основанные на событиях) отделены от моделей для чтения (оптимизированных для запросов).