DDD, графики на обекти и източник на събития

въведение

Този въпрос е относно DDD и източник на събития, където субектите в рамките на Aggregate, различни от Aggregate Root, имат поведение при генериране на събития.

Пример

Това, което следва, е пример за ситуацията, която описвам, където съм сигурен, че искам да капсулирам някаква логика в други обекти в рамките на агрегата. Това може да включва спиране на неверието по отношение на действителния пример и дали той е добър модел или не. :)

Опитвам се да моделирам DeliveryRun Съвкупен корен (AR), което е пътуването, което превозното средство прави, за да извърши доставка. Преди да тръгне, трябва да има актуален DeliveryManifest. „Актуалността“ на това ми подсказва, че DeliveryManifest е обект в рамките на границата на последователност DeliveryRun, дефинирана от AR.

Добре засега.

Използвам подход за източник на събития за това - подходът, както се преподава от Greg Young и имплементиран в библиотеката Regalo. Това означава, че AR (DeliveryRun) всъщност не трябва да има никакви обекти, ако няма поведение за тях (напр. SalesOrder може да няма SalesOrderLines, защото вместо това записва събития като ItemsAdded/ItemsRemoved).

Все пак трябва да има някаква логика около DeliveryManifest. По-конкретно, след като манифестът е заявен за първи път, когато елементите се добавят към доставката, трябва да се създаде нова версия на манифеста. Това означава, че можем да гарантираме, че шофьорите няма да потеглят без най-актуалния наличен манифест.

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

Варианти, които обмислям

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

  • Трябва ли да няма DeliveryManifest (освен може би като структура от данни) и цялата логика/събития да се изпълняват директно от DeliveryRun?

  • Трябва ли DeliveryManifest да бъде негов собствен AR и да се уверите, че DeliveryRun е уведомен за идентификатора на текущия манифест? Тъй като това извежда обекта на манифеста извън границата на съгласуваност на DeliveryRun, ще трябва да създам някаква обработка на събития, за да се абонирам за промени в DeliveryRun, които са от значение за манифеста, така че да може да бъде съответно актуализиран/невалиден и т.н.

  • Приложете различен стил за улавяне на събитията, подобен на модела DomainEvents на Udi. Това означава промяна на библиотеката 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