В основном, вопрос заключается в следующем:
как правильно построить хранилище событий для системы, получающей события, которая должна иметь возможность:
преобразовать агрегат в другой,
сохранить тот же идентификатор,
и все же сможете восстановить его из потока событий?
Теперь мой пример:
у меня есть ProspectiveCustomer
которые могут быть преобразованы в PayingCustomer
как это :
ProspectiveCustomer::convertToPayingCustomer(ProspectiveCustomerId $id)
PayingCustomer будет сохранять тот же Id, чтобы можно было отслеживать его время жизни.
А сейчас представьте себе следующее событие Stream :
Давайте сосредоточимся на пункте 4):
У нас будет commandHandler, который получает paymentCommand {customerId: «123», сумма: «500 €»}.
Его работа будет заключаться в следующем:
Мой вопрос о 1) воссоздании из истории:
Сервис EventStorage будет:
Стопка событий теперь будет содержать:
Как мог командный обработчик PayingCustomer::reconstituteFromHistory(EventsHistory $events)
в то время как события $ являются событиями, выпущенными из / применимыми к ProspectiveCustomer
В настоящее время я решаю проблему с PayingCustomer, имеющим собственный Id, но с ссылкой на ProspectiveCustomerId.
Но учитывая это:
это кажется грязным, потому что модель сейчас загрязнена двумя идентификаторами, тогда как одного должно быть достаточно.
Если бы это была не система, основанная на событиях, я бы определенно выбрал один уникальный идентификатор.
Это, как говорится, и учитывая Event-Sourcing — это просто реализация подробно, я ищу способ, чтобы оба агрегата с одинаковым идентификатором.
У вас есть два агрегата, но нет «конверсии». Вы вступаете на опасную дорогу, которая может привести вас к конвертации тележек для покупок (например).
У вас уже есть два разные Концепции — потенциальный клиент и платящий клиент. Вероятно, они были идентифицированы во время ваших бесед с экспертами по предметной области. Это ясно означает две совокупности, иногда два ограниченных контекста. Вы не должны делать никаких преобразований, но вы определенно можете создавать новые агрегаты, реагирующие на то, что происходит в вашей системе (заказ принят).
Я также ожидаю, что термин «конверсия» пришел к вам от экспертов в области. Это нормально, поскольку в отделе продаж они используют эту терминологию, чтобы указать, что кто-то, кто был заинтересован, действительно совершил покупку. Они действительно называют это «преобразованием», и вы были бы правы, включив его в свой вездесущий язык, используя «3. Потенциальный клиент переоборудованный«но это не имеет ничего общего с технический преобразование, означающее изменение типа объекта.
Вам нужны обработчики событий домена, которые будут выполнять (3) и (4), поскольку вы говорите, что это тот же ограниченный контекст.
Генерация уникального идентификатора агрегата не является функцией самого агрегата, она выполняется снаружи, и агрегаты получают свою идентификацию, когда создаются как метод фабрики или / и параметр конструктора. Поэтому, когда вы создаете платного клиента из потенциального клиента, ничто не мешает вам использовать одну и ту же личность.
Однако у вас начинают появляться предположения, что вы всегда ожидаете, что у вас будет потенциальный клиент, чтобы получить вашу историю или что-то еще, используя ту же самую идентичность. Поскольку это предположение неявно, оно легко забывается и вообще не рекомендуется в DDD (не забывайте делать явные вещи неявными). Вы можете легко сохранить идентификационную ссылку на потенциального клиента в новом платящем клиенте, и тогда у вас все будет в порядке.
Моя первая мысль, что это один и тот же агрегат, но с разными состояниями. Но, как указано в комментариях к вопросу, они должны быть двумя разными агрегатами.
Когда вы конвертируете свой агрегат, вы действительно создаете новый агрегат, поэтому я решил бы это с помощью обработчика событий домена. Обработчик событий домена будет реагировать на события и выдавать команды, так что пусть ваш ProspectiveCustomer
отправить что-то вроде OfferAcceptedEvent
что обработчик события может действовать.
Это может быть потоком процесса:
ProspectiveCustomer
депеши OfferAcceptedEvent
,OfferAcceptedEvent
и отправляет CreatePayingCustomerCommand
, (The OfferAcceptedEvent
должен содержать все необходимые данные ProspectiveCustomer
создать команду)PayingCustomer
созданоЭто, вероятно, хорошая идея, чтобы включить ProspectiveCustomerId
в PayingCustomerCreatedEvent
так что вы можете отслеживать PayingCustomer
назад к ProspectiveCustomer
,