DDD, графы объектов и поиск событий

вступление

Этот вопрос касается DDD и источника событий, где объекты в составе агрегата, отличного от агрегированного корня, имеют поведение, генерирующее события.

Пример

Далее следует пример описываемой мной ситуации, в которой я уверен, что хочу инкапсулировать некоторую логику внутри других сущностей внутри Aggregate. Это может означать приостановку недоверия к реальному примеру и тому, является ли он хорошей моделью или нет. :)

Я пытаюсь смоделировать DeliveryRun агрегатный корень (AR), который представляет собой поездку, которую совершает транспортное средство для выполнения доставки. Перед отправкой необходимо обновить DeliveryManifest. «Актуальность» этого подсказывает мне, что DeliveryManifest будет сущностью в пределах DeliveryRun границы согласованности, определенной AR.

Ладно пока.

Я использую для этого подход Event Sourcing - подход, которому научил Greg Young и реализован в библиотеке Regalo. Это означает, что AR (DeliveryRun) фактически не должен иметь никаких сущностей, если для них нет поведения (например, SalesOrder может не иметь SalesOrderLines, потому что вместо этого он записывает такие события, как _8 _ / _ 9_).

Однако в отношении DeliveryManifest должна быть некоторая логика. В частности, после первого запроса манифеста при добавлении элементов в доставку необходимо создать новую версию манифеста. Это означает, что мы можем гарантировать, что водители не уезжают, пока не будет доступен самый последний манифест.

Если бы мне пришлось инкапсулировать логику внутри объекта DeliveryManifest (который не будет сериализован и сохранен; мы используем Event Sourcing, а не AR), как мне фиксировать события?

Варианты, которые я рассматриваю

  • Должны ли события генерироваться объектом DeliveryManifest, но сохраняться для самого DeliveryRun (которому тогда нужно знать, как воспроизвести эти события в DeliveryManifest при загрузке из хранилища событий)?

  • Должно ли быть не DeliveryManifest (кроме, возможно, структуры данных), и вся логика / события должны быть реализованы непосредственно DeliveryRun?

  • Должен ли DeliveryManifest быть его собственным AR и убедиться, что DeliveryRun сообщает идентификатор текущего манифеста? Поскольку это выводит объект манифеста за пределы согласованности DeliveryRun, мне нужно будет создать некоторую обработку событий, чтобы подписаться на изменения в DeliveryRun, которые имеют отношение к манифесту, чтобы его можно было обновить / сделать недействительным и т. Д. Соответственно.

  • Реализуйте другой стиль для захвата событий, аналогичный шаблону Udi DomainEvents. Это означает изменение библиотеки Regalo, хотя я думаю, что ее можно довольно легко сделать для поддержки обоих шаблонов. Это позволит захватывать все события, генерируемые всеми объектами в совокупности, чтобы их можно было сохранить для AR. Мне нужно было подумать о решении для загрузки / воспроизведения, хотя ...


person Neil Barnwell    schedule 08.02.2013    source источник
comment
Не хотите ли объяснить, как программное обеспечение предотвращает выезд водителя грузовика с устаревшим манифестом? Вы можете уменьшить риск того, что это когда-либо случится, но не предотвратить его. Кроме того, вам может потребоваться книга записей о том, что на самом деле произошло (даже если манифест был неправильным, при этом конкретном прогоне вы хотите знать, какой манифест был взят с собой) по сравнению с тем, что было запланировано.   -  person Yves Reynhout    schedule 08.02.2013
comment
Поразмыслив, мне кажется, что жизненный цикл манифеста и прогона разные, но в какой-то момент случается, что они перекрываются. Манифест может меняться много раз до того, как начнется фактическая доставка. При планировании доставки вы можете указать, какой манифест нужно взять с собой. Возможно, людям следует прекратить вносить изменения в манифесты после того, как они запланированы к доставке (может быть, нет, или, может быть, есть момент времени до фактической доставки, до которого люди могут изменить манифест). Ясно, что мне не хватает знаний в предметной области ...   -  person Yves Reynhout    schedule 08.02.2013
comment
Хорошо, я ожидал получить подобные комментарии по вопросу, связанному с DDD, но на самом деле мой вопрос должен был основываться на предположении, что я смоделировал его правильно, и, следовательно, как я могу реализовать инкапсуляцию логики в сущностях, отличных от AR ? Если я буквально пишу код C # в методе поведения на моем AR, может ли он делегировать другим классам сущностей, которые инкапсулируют логику? Если да, то как можно фиксировать события, которые генерируют эти сущности? Если они сами ничего не генерируют, откуда берутся события?   -  person Neil Barnwell    schedule 08.02.2013


Ответы (2)


Я бы не стал делать DeliveryManifest еще один агрегированный корень, если это не граница согласованности.

Многие образцы не решают эту проблему. Похоже, что совокупный корень должен быть обязан собирать события от сущностей внутри него и передавать их правильным сущностям для загрузки позже, что, по-видимому, является вашим вариантом 1.

Вариант 2 также отлично подходит, если с DeliveryManifest не связано никакого поведения.

person James Nugent    schedule 08.02.2013
comment
Как указал Ив, действительно требуется более тщательный анализ вариантов использования, чтобы узнать, какой подход выбрать, но если вы сделаете правильный выбор для своего домена, кажется, что ваши варианты соответствуют: (1) - реализовать как объект, (2) реализовать как объект значения, (3) реализовать как отдельный агрегатный корень - person James Nugent; 08.02.2013

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

public class DeliveryRun {
  Dictionary<Type, Action<object>> _handlers = new Dictionary<Type, Action<object>>();
  List<object> _events = new List<object>();

  DeliveryManifest _manifest;

  public DeliverRun() {
    Register<DeliveryManifestAssigned>(When);
    Register<DeliveryManifestChanged>(When);
  }

  public void AssignManifest(...) {
    Apply(new DeliveryManifestAssigned(...));
  }

  public void ChangeManifest(...) {
    _manifest.Change(...);
  }

  public void Initialize(IEnumerable<object> events) {
    foreach(var @event in events) Play(@event);
  }

  internal void NotifyOf(object @event) {
    Apply(@event);
  }

  void Register<T>(Action<T> handler) {
    _handlers.Add(typeof(T), @event => handler((T)@event));
  }

  void Apply(object @event) {
    Play(@event);
    Record(@event);
  }

  void Play(object @event) {
    Action<object> handler;
    if(_handlers.TryGet(@event.GetType(), out handler)) {
      handler(@event); //dispatches to those When methods
    }
  }

  void Record(object @event) {
    _events.Add(@event);
  }

  void When(DeliveryManifestAssigned @event) {
    _manifest = new DeliveryManifest(this);
    _manifest.Initialize(@event);
  }

  void When(DeliverManifestChanged @event) {
    _manifest.Initialize(@event);
  }
}

public class DeliveryManifest {
  Dictionary<Type, Action<object>> _handlers = new Dictionary<Type, Action<object>>();
  DeliveryRun _run;

  public DeliveryManifest(DeliveryRun run) {
    _run = run;
    Register<DeliveryManifestAssigned>(When);
    Register<DeliveryManifestChanged>(When);
  }

  public void Initialize(object @event) {
    Play(@event);
  }

  public void Change(...) {
    Apply(new DeliveryManifestChanged(...));
  }

  void Register<T>(Action<T> handler) {
    _handlers.Add(typeof(T), @event => handler((T)@event));
  }

  void Play(object @event) {
    Action<object> handler;
    if(_handlers.TryGet(@event.GetType(), out handler)) {
      handler(@event); //dispatches to those When methods
    }
  }

  void Apply(object @event) {
    _run.NotifyOf(@event);
  }

  void When(DeliveryManifestAssigned @event) {
    //...  
  }

  void When(DeliveryManifestChanged @event) {
    //...  
  }
}

P.S. Я закодировал это «из головы», простите, пожалуйста, за ошибки компиляции.

person Yves Reynhout    schedule 08.02.2013
comment
Отлично, спасибо! В настоящее время я предлагаю подход, при котором AR передает себя в сущности как EventSource. Затем сущности вызывают Record () аналогично AR, которые я создавал до сих пор, но на самом деле это просто вызывает AggregateRoot.Record (), поэтому все события записываются в AR. Воспроизведение решается с помощью хитроумного трюка в Regalo, где он будет искать и вызывать методы Apply () для события и всех его базовых классов. Что-то вроде наследования методов, если хотите. В какой-то момент я построю пример в Vita, чтобы показать, как он работает, если я решу его придерживаться. - person Neil Barnwell; 09.02.2013