Как создать новый агрегатный корень в CQRS?

10

Как мы должны создавать новые агрегатные корни в архитектуре cqrs? В этом примере я хочу создать новый совокупный корневой AR2, который содержит ссылку на первый AR1.

Я создаю AR2, используя метод AR1 в качестве отправной точки. Пока что вижу несколько вариантов:

  1. Внутри метода в AR1 createAr2RootOpt1я мог бы new AR2()немедленно вызвать и сохранить этот объект в БД, используя доменную службу, которая имеет доступ к хранилищу.
  2. Я мог бы выдать событие в первом агрегатном корне, например. 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());
        }
    }
    

Какой более правильный способ сделать это?

Боян Вукасович
источник

Ответы:

2

Я думаю, что варианта нет. 2 - это решение с небольшой, но важной модификацией: AR1не должно генерировать событие, целью которого является создание AR2, вместо этого оно должно генерировать AR1WasCreatedсобытие. Это событие должно сохраняться в хранилище событий, так как это важное событие, отмечающее рождение AR1. Затем Sagawhould listent для AR1WasCreatedсобытия и сформировать команду для создания AR2: CreateAR2Command.

Вариант № 1 очень неправильный. Вы никогда не должны внедрять такого рода доменную службу в Aggregate. Aggregatesдолжен быть чистым, без побочных эффектов, кроме генерации событий.

PS Я никогда не генерирую события из конструктора, Aggregateпоскольку существует различие между созданием экземпляра объекта (в смысле языка программирования) и созданием (рождением, если хотите) объекта Aggregate. Я генерирую события только из handleметода (при обработке command).

Константин Гальбену
источник
Что вы имеете в виду AR1WasCreated? Должно ли это быть AR2WasCreated? Кроме того, если я использую вашу логику, я генерирую событие AR2WasCreatedдо того, как оно действительно будет создано? И сохранение этого события в журнале событий AR1 кажется проблематичным, так как я на самом деле не нуждаюсь в этих данных внутри AR1 (это ничего не меняет в AR1).
Боян Вукасович
ОК, 3 года спустя. Это идет AR1WasCreated-> SAGA (есть правило, если A1 был создан, то создайте A2) -> CreateAR2Command-> AR2WasCreated.
Боян Вукасович
@ BojanVukasovic Я рад, что это сработало так, как я написал :)
Константин Гальбену
2

Как мы должны создавать новые агрегатные корни в архитектуре cqrs?

Шаблоны создания странные .

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

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

Ваш второй подход тоже в порядке. «События, которые мы генерируем без фактического сохранения в базе данных», иногда называют «событиями домена»

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

Примечание: в системах с источником событий вы обычно не используете события таким образом.

В случае использования источника событий ShouldCreateAR2Event не будет сохранено в хранилище событий, поскольку оно не влияет на состояние первого совокупного корня.

Примечание: имена событий, как правило, в прошедшем времени - IfCrateAR2 имеет неправильное написание.

Да, если вы просто выбрасываете событие на синхронную шину для запуска удаленного кода, вам не следует сохранять это событие в книге рекордов. Это просто деталь реализации в таком масштабе.

Или мы все еще должны сохранить это в хранилище событий?

Избегайте изменения двух разных потоков событий в одной транзакции. Если это создание также представляет изменение AR1, то обычным ответом будет изменение AR1 в этой транзакции с асинхронным подписчиком на те события, которые отвечают за запуск команды для создания AR2.

Здесь очень помогает обработка идемпотентных команд.

VoiceOfUnreason
источник
Спасибо за ответ. Еще одна вещь, которая не ясна на 100% - позже, если мне придется использовать AR2 в качестве аргумента AR1, как я должен передать это - поскольку CQRS утверждает, что AR следует использовать только для записи, а не для запросов. Но у меня нет другого выбора, кроме как использовать, AR1.doSmthn(AR2 param)поскольку в любой создаваемой мной проекции чтения нет полных данных, которые мне нужны (только AR2 имеет полные данные).
Боян Вукасович
> «Да, если вы просто выбрасываете событие на синхронную шину для запуска удаленного кода, вам не следует сохранять это событие в книге рекордов». Я думаю, что сохранение его имеет реальную ценность, поскольку вы знаете, что процесс запущен, и что-то должно произойти, теперь вы также можете отслеживать, действительно ли это завершено. Но я думаю, это зависит от
варианта