DDD Событие источника поднять событие для созданного объекта - PullRequest
0 голосов
/ 06 февраля 2020

У меня есть класс Category, который имеет свойство children. При создании категории я вызываю событие CategoryCreated в конструкторе, который регистрирует это событие в BaseCategory. Также у меня есть метод apply в Category, который применяет события к состоянию.

 public class Category :BaseCategory
{
    public Category(string id, TranslatableString name, DateTime timestamp)
    {
        Raise(new CategoryCreated(id, name, timestamp));
    }
}
  public override void Apply(DomainEvent @event)
    {
        switch (@event)
        {
            case CategoryCreated e:
                 this.Id = e.Id;
                 this.Name = e.Name;
                 break;
                ...

Теперь предположим, что я хочу создать Category и добавить к ней дочерний элемент.

var category = new Category("1","2",DateTime.UtcNow);
category.AddChild("some category", "name", DateTime.UtcNow);
foreach(var e in category.UncomittedEvents)
{  
    category.Apply(e);
}

При добавлении дочернего элемента я задаю закрытое свойство ParentId вновь созданной категории в качестве идентификатора родителя.

 public void AddChild(string id, string name,DateTime date)
    {
        if (string.IsNullOrWhiteSpace(id))
            throw new ArgumentNullException(nameof(id));
        if (Children.Any(a => a.Id== Id))
            throw new InvalidOperationException("Category already exist ");
        Raise(new CategoryAdded(Guid.NewGuid().ToString(), this.Id/*parent id*/, name, DateTime.UtcNow));
    }


  public class CategoryAdded : DomainEvent
    {
        public CategoryAdded(string id, string parentId, string name, DateTime timestamp) {}
    }

Проблема в том, что при применении событий идентификатор родителя будет нулевым, поскольку события еще не применены, а свойство идентификатора родителя, переданное в качестве идентификатора родителя, равно нулю:

new CategoryAdded(Guid.NewGuid().ToString(), this.Id /*parent id*/, name, DateTime.UtcNow)

Где ошибка проектирования? Где и когда должно быть инициировано событие CategoryCreated? Как бы вы справились с этой ситуацией?

Ответы [ 2 ]

2 голосов
/ 07 февраля 2020

Где ошибка дизайна? Где и когда должно быть инициировано событие 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, который используется в обеих настройках.

2 голосов
/ 06 февраля 2020

Где ошибка дизайна?

Ваш метод Raise (...) также должен вызывать Apply. Помните, что ваш агрегат отвечает за поддержание согласованного состояния. Применение событий за пределами вашего Агрегата нарушает этот принцип.

protected void Raise(DomainEvent @event)
{
    this.Apply(@event);
    this.UncomittedEvents.Add(@event);
}
...