Как создать новый агрегатный корень в 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.

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


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

> «Да, если вы просто выбрасываете событие на синхронную шину для запуска удаленного кода, вам не следует сохранять это событие в книге рекордов». Я думаю, что сохранение его имеет реальную ценность, поскольку вы знаете, что процесс запущен, и что-то должно произойти, теперь вы также можете отслеживать, действительно ли это завершено. Но я думаю, это зависит от
варианта
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.