Источник событий: воспроизведение событий для дочерних объектов в совокупности

Когда дело доходит до воспроизведения событий для совокупности, как вы Apply эти события для дочерних (некорневых) объектов.

Пока у меня есть две идеи о том, как можно это сделать.

  1. Получить Aggregate Root для направления событий в соответствующие объекты
  2. Есть Aggregate Loader, который загружает объекты по идентификатору и напрямую применяет их события

Какие подходы вы выбрали, что сработало, а что нет?


В моем поиске я нашел только две ссылки, которые обсуждают проблему (обе выбрали первый подход):

Комплексные агрегатные структуры (4.2.3.)
Совокупные корни, координирующие свои объекты в системе источников событий

1 ответ

Решение

Будучи одним из участников вышеупомянутой дискуссии, я могу поделиться некоторыми соображениями. Если вы посмотрите на проект, подобный NCQRS, который формализовал, как сущности строятся и гидратируются довольно явно, вы заметите, что с этим подходом есть определенная жесткость. Для сущностей я обнаружил, что я эволюционирую их хранилище как состояние в совокупности от простого поля, списка или словаря до выделенного класса коллекции в зависимости от того, как рассеянное поведение становится для их обслуживания. Меньшая жесткость приносит свободу, выбор моделирования в пределах совокупной границы. Я это очень ценю.

Маршрутизация событий при регидратации происходит внутри агрегата. Это не то, что надо выводить из ИМО. Есть разные способы сделать это. В моем собственном проекте я очень легко формализовал его (здесь показана только сущность):

/// <summary>
/// Base class for aggregate entities that need some basic infrastructure for tracking state changes on their aggregate root entity.
/// </summary>
public abstract class Entity : IInstanceEventRouter
{
    readonly Action<object> _applier;
    readonly InstanceEventRouter _router;

    /// <summary>
    /// Initializes a new instance of the <see cref="Entity"/> class.
    /// </summary>
    /// <param name="applier">The event player and recorder.</param>
    /// <exception cref="System.ArgumentNullException">Thrown when the <paramref name="applier"/> is null.</exception>
    protected Entity(Action<object> applier)
    {
        if (applier == null) throw new ArgumentNullException("applier");
        _applier = applier;
        _router = new InstanceEventRouter();
    }

    /// <summary>
    /// Registers the state handler to be invoked when the specified event is applied.
    /// </summary>
    /// <typeparam name="TEvent">The type of the event to register the handler for.</typeparam>
    /// <param name="handler">The state handler.</param>
    /// <exception cref="System.ArgumentNullException">Thrown when the <paramref name="handler"/> is null.</exception>
    protected void Register<TEvent>(Action<TEvent> handler)
    {
        if (handler == null) throw new ArgumentNullException("handler");
        _router.ConfigureRoute(handler);
    }

    /// <summary>
    /// Routes the specified <paramref name="event"/> to a configured state handler, if any.
    /// </summary>
    /// <param name="event">The event to route.</param>
    /// <exception cref="ArgumentNullException">Thrown when the <paramref name="event"/> is null.</exception>
    public void Route(object @event)
    {
        if (@event == null) throw new ArgumentNullException("event");
        _router.Route(@event);
    }

    /// <summary>
    /// Applies the specified event to this instance and invokes the associated state handler.
    /// </summary>
    /// <param name="event">The event to apply.</param>
    protected void Apply(object @event)
    {
        if (@event == null) throw new ArgumentNullException("event");
        _applier(@event);
    }
}

Маршрутизация событий во время выполнения поведения следует контурам жизненного цикла объекта: создание, изменение и удаление. Во время создания метод Apply создает новую сущность (помните, что это единственное место, где мы можем изменять состояние) и назначает его полю, добавляет его в список, словарь или пользовательскую коллекцию. Модификация в методе Apply обычно включает поиск затронутой сущности или сущностей и либо направляет событие на сущность, либо имеет выделенные внутренние методы на сущности, которые применяют изменения, используя данные из события. При удалении метод Apply выполняет поиск и удаляет затронутую сущность или сущности. Обратите внимание, как эти методы Apply смешиваются с фазой регидратации, чтобы достичь того же состояния.

Теперь важно понимать, что могут существовать другие виды поведения, которые влияют на сущности, но не "принадлежат" какой-либо конкретной сущности (так сказать, нет сопоставления один к одному). Это просто то, что происходит и имеет побочный эффект на одну или несколько сущностей. Именно такие вещи заставляют вас ценить гибкость дизайна сущностей.

Другие вопросы по тегам