Источник событий: как преобразовать aggregateRoot в другой

В основном, вопрос заключается в следующем:

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

  • преобразовать агрегат в другой,

  • сохранить тот же идентификатор,

  • и все же сможете восстановить его из потока событий?

Теперь мой пример:

у меня есть ProspectiveCustomer которые могут быть преобразованы в PayingCustomer как это :

ProspectiveCustomer::convertToPayingCustomer(ProspectiveCustomerId $id)

PayingCustomer будет сохранять тот же Id, чтобы можно было отслеживать его время жизни.

А сейчас представьте себе следующее событие Stream :

  1. Добавлен потенциальный клиент к CRM
  2. Перспективному клиенту было сделано предложение
  3. Потенциальный клиент принял предложение и поэтому был преобразован в PayingCustomer
  4. PayingCustomer оплатил свой счет

Давайте сосредоточимся на пункте 4):

У нас будет commandHandler, который получает paymentCommand {customerId: «123», сумма: «500 €»}.
Его работа будет заключаться в следующем:

  1. Собрать PayingCustomer из истории событий
  2. позвоните PayingCustomer :: pay (сумма денег $)

Мой вопрос о 1) воссоздании из истории:

Сервис EventStorage будет:

  1. ищите AggregateId
  2. загружает события (SELECT * FROM Events WHERE ID = ‘xxx’)

Стопка событий теперь будет содержать:

  • ProspectiveCustomerWasAdded
  • ProspectiveCustomerWasMadeAnOffer
  • ProspectiveCustomerAcceptedTheOffer

Как мог командный обработчик PayingCustomer::reconstituteFromHistory(EventsHistory $events) в то время как события $ являются событиями, выпущенными из / применимыми к ProspectiveCustomer

РЕДАКТИРОВАТЬ

В настоящее время я решаю проблему с PayingCustomer, имеющим собственный Id, но с ссылкой на ProspectiveCustomerId.

Но учитывая это:

  1. это тот же ограниченный контекст,
  2. очень жизненный цикл того же клиента (ProspectiveCustomer заканчивается при запуске PayingCustomer),

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

Если бы это была не система, основанная на событиях, я бы определенно выбрал один уникальный идентификатор.

Это, как говорится, и учитывая Event-Sourcing — это просто реализация подробно, я ищу способ, чтобы оба агрегата с одинаковым идентификатором.

3

Решение

У вас есть два агрегата, но нет «конверсии». Вы вступаете на опасную дорогу, которая может привести вас к конвертации тележек для покупок (например).

У вас уже есть два разные Концепции — потенциальный клиент и платящий клиент. Вероятно, они были идентифицированы во время ваших бесед с экспертами по предметной области. Это ясно означает две совокупности, иногда два ограниченных контекста. Вы не должны делать никаких преобразований, но вы определенно можете создавать новые агрегаты, реагирующие на то, что происходит в вашей системе (заказ принят).

  1. Создан потенциальный клиент
  2. Предложение принято
  3. Платежный клиент создан от потенциального клиента
  4. Потенциальный клиент удален (или помечен как «преобразованный», или деактивированный)

Я также ожидаю, что термин «конверсия» пришел к вам от экспертов в области. Это нормально, поскольку в отделе продаж они используют эту терминологию, чтобы указать, что кто-то, кто был заинтересован, действительно совершил покупку. Они действительно называют это «преобразованием», и вы были бы правы, включив его в свой вездесущий язык, используя «3. Потенциальный клиент переоборудованный«но это не имеет ничего общего с технический преобразование, означающее изменение типа объекта.

Вам нужны обработчики событий домена, которые будут выполнять (3) и (4), поскольку вы говорите, что это тот же ограниченный контекст.

Генерация уникального идентификатора агрегата не является функцией самого агрегата, она выполняется снаружи, и агрегаты получают свою идентификацию, когда создаются как метод фабрики или / и параметр конструктора. Поэтому, когда вы создаете платного клиента из потенциального клиента, ничто не мешает вам использовать одну и ту же личность.

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

1

Другие решения

Моя первая мысль, что это один и тот же агрегат, но с разными состояниями. Но, как указано в комментариях к вопросу, они должны быть двумя разными агрегатами.

Когда вы конвертируете свой агрегат, вы действительно создаете новый агрегат, поэтому я решил бы это с помощью обработчика событий домена. Обработчик событий домена будет реагировать на события и выдавать команды, так что пусть ваш ProspectiveCustomer отправить что-то вроде OfferAcceptedEvent что обработчик события может действовать.

Это может быть потоком процесса:

  1. Пользователь принимает предложение и ProspectiveCustomer депеши OfferAcceptedEvent,
  2. Обработчик событий реагирует на OfferAcceptedEvent и отправляет CreatePayingCustomerCommand, (The OfferAcceptedEvent должен содержать все необходимые данные ProspectiveCustomer создать команду)
  3. PayingCustomer создано

Это, вероятно, хорошая идея, чтобы включить ProspectiveCustomerId в PayingCustomerCreatedEvent так что вы можете отслеживать PayingCustomer назад к ProspectiveCustomer,

1