Как мы должны создавать новые агрегатные корни в архитектуре cqrs? В этом примере я хочу создать новый совокупный корневой AR2, который содержит ссылку на первый AR1.
Я создаю AR2, используя метод AR1 в качестве отправной точки. Пока что вижу несколько вариантов:
- Внутри метода в AR1
createAr2RootOpt1
я мог быnew AR2()
немедленно вызвать и сохранить этот объект в БД, используя доменную службу, которая имеет доступ к хранилищу. Я мог бы выдать событие в первом агрегатном корне, например.
SholdCreateAR2Event
и затем иметь сагу без сохранения состояния, которая реагирует на это и выдает команду,CreateAR2Command
которая затем обрабатывается и фактически создает AR2 и испускаетAR2CreatedEvent
. В случае использования источник событийSholdCreateAR2Event
не будет сохранен в хранилище событий, так как он не влияет на состояние первого совокупного корня. (Или мы все еще должны сохранить это в хранилище событий?)class AR1{ Integer id; DomainService ds; //OPTION 1 void createAr2RootOpt1(){ AR2 ar2 = new AR2(); ds.saveToRepo(ar2); } //OPTION 2 void createAr2RootOpt2(){ publishEvent(new SholdCreateAR2Event()); //we don't need this event. Shoud it still be preserved in event store? } } class AR2{ Integer id; Integer ar1Id; void handle(CreateAR2Command command){ //init this AR with values and save publishEvent(AR2CreatedEvent()); //used for projections afterwards and saved inside AR2 event store } } class Saga{ void handle(SholdCreateAR2Event ev){ emitCommand(new CreateAR2Command()); } }
Какой более правильный способ сделать это?
источник
AR1WasCreated
? Должно ли это бытьAR2WasCreated
? Кроме того, если я использую вашу логику, я генерирую событиеAR2WasCreated
до того, как оно действительно будет создано? И сохранение этого события в журнале событий AR1 кажется проблематичным, так как я на самом деле не нуждаюсь в этих данных внутри AR1 (это ничего не меняет в AR1).AR1WasCreated
-> SAGA (есть правило, если A1 был создан, то создайте A2) ->CreateAR2Command
->AR2WasCreated
.Шаблоны создания странные .
Уди Дахан может сказать несколько полезных слов об общей проблеме: не создавайте совокупные корни . Суть в том, что агрегаты не просто появляются из ниоткуда, и что существует язык доменов, который описывает, как они выглядят, которые должны быть отражены в модели вашего домена.
Склонность к искажению заключается в том, что сущность в модели вашего домена, которая обрабатывает команду, не является сущностью, изменяемой транзакцией. Это не так; это просто странно (по сравнению со случаями, когда вы просите сущность изменить себя).
Ваш второй подход тоже в порядке. «События, которые мы генерируем без фактического сохранения в базе данных», иногда называют «событиями домена»
Основная идея заключается в том, что в одной и той же транзакции обработчик команд вызывает событие, которое перемещается по шине к обработчику событий, который позволяет создать второй агрегат. Возможно, вы получите немного лучшую целостность кода.
Примечание: в системах с источником событий вы обычно не используете события таким образом.
Примечание: имена событий, как правило, в прошедшем времени - IfCrateAR2 имеет неправильное написание.
Да, если вы просто выбрасываете событие на синхронную шину для запуска удаленного кода, вам не следует сохранять это событие в книге рекордов. Это просто деталь реализации в таком масштабе.
Избегайте изменения двух разных потоков событий в одной транзакции. Если это создание также представляет изменение AR1, то обычным ответом будет изменение AR1 в этой транзакции с асинхронным подписчиком на те события, которые отвечают за запуск команды для создания AR2.
Здесь очень помогает обработка идемпотентных команд.
источник
AR1.doSmthn(AR2 param)
поскольку в любой создаваемой мной проекции чтения нет полных данных, которые мне нужны (только AR2 имеет полные данные).