Где ошибка дизайна? Где и когда должно быть инициировано событие CategoryCreated? Как бы вы справились с этой ситуацией?
ОК, это не ваша вина. В литературе отстой .
Ответ CPearson показывает общий механизм устранения симптомов, но я думаю, что важно видеть, что происходит.
Если мы применяем шаблон «источник событий» в чистом виде, наша модель данных будет выглядеть как поток событий:
class Category {
private final List[Event] History;
}
Изменения в текущем состоянии будут достигается путем добавления событий в историю.
public Category(string id, TranslatableString name, DateTime timestamp) {
History.Add(new CategoryCreated(id, name, timestamp));
}
И запросы текущего состояния будут методами , которые будут искать в истории событий поиск данных.
public Id Id() {
Id current = null;
History.forEach( e -> {
if (e instance of CreatedEvent) {
current = CreatedEvent.Id(e)
}
});
return current
}
Хорошая новость заключается в том, что дизайн относительно прост в принципе. Плохая новость заключается в том, что производительность ужасна - чтение, как правило, гораздо чаще встречается и пишется, но каждый раз, когда мы хотим что-то прочитать, нам приходится go просматривать события, чтобы найти ответ.
Это не всегда это плохо - свойства, которые являются постоянными на протяжении всего жизненного цикла объекта, обычно появляются в первом событии; чтобы получить самую последнюю версию свойства, вы можете часто перечислять историю в обратном направлении и останавливаться на первом (самом последнем) совпадении.
Но это все еще довольно неловко. Поэтому для повышения производительности запросов мы кешируем интересные результаты в свойствах, эффективно используя снимок для ответа на запросы. Но чтобы это работало, нам нужно обновить кэшированные значения (снимок), когда мы добавляем новые события в историю.
Таким образом, метод Raise должен выполнять две вещи: модифицировать историю событий и модифицировать снимок. Изменение истории событий является общим назначением, поэтому работа часто разделяется на общий базовый класс; но моментальный снимок задает c для коллекции результатов запроса, которую мы хотим кэшировать, поэтому этот бит обычно реализуется внутри самого «агрегата root».
Поскольку снимок при восстановлении агрегата из события, хранящиеся в нашей базе данных, должны соответствовать оперативной копии, эта схема часто включает метод Apply, который используется в обеих настройках.