Comment créer une nouvelle racine agrégée dans CQRS?

10

Comment créer de nouvelles racines agrégées dans l'architecture CQRS? Dans cet exemple, je veux créer une nouvelle racine agrégée AR2 qui contient une référence à la première AR1.

Je crée AR2 en utilisant la méthode AR1 comme point de départ. Jusqu'à présent, je vois peu d'options:

  1. Dans la méthode AR1, createAr2RootOpt1je pourrais appeler new AR2()et enregistrer cet objet dans db immédiatement en utilisant le service de domaine qui a accès au référentiel.
  2. Je pourrais émettre un événement dans la première racine agrégée, par exemple. SholdCreateAR2Eventpuis avoir une saga sans état qui réagit à ce sujet et émet une commande CreateAR2Commandqui est ensuite gérée et crée réellement AR2 et émet AR2CreatedEvent. En cas d'utilisation, le sourcing d'événements SholdCreateAR2Eventne serait pas conservé dans le magasin d'événements, car il n'affecte pas l'état de la première racine agrégée. (Ou devrions-nous toujours l'enregistrer dans le magasin d'événements?)

    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());
        }
    }
    

Quelle est la meilleure façon de procéder?

Bojan Vukasovic
la source

Réponses:

2

Je pense que cette option non. 2 est la solution, avec une petite mais importante modification: AR1ne devrait pas émettre un événement dont le but est de créer le AR2, mais devrait plutôt émettre un AR1WasCreatedévénement. Cet événement doit être conservé dans le magasin d'événements, car il s'agit d'un événement important marquant la naissance de AR1. Ensuite, un Sagalistent whould pour AR1WasCreatedévénement et générer une commande pour créer AR2: CreateAR2Command.

L'option n ° 1 est très fausse. Vous ne devez jamais injecter ce type de service de domaine dans un Aggregate. Aggregatesdevrait être pur, sans effets secondaires autres que la génération d'événements.

PS Je n'émets jamais d'événements du constructeur du Aggregatecar il y a une distinction entre créer une instance d'un objet (au sens du langage de programmation) et la création (la naissance si vous voulez) d'un Aggregate. J'émets des événements uniquement à partir d'une handleméthode (lors de la gestion de a command).

Constantin Galbenu
la source
Qu'entendez-vous par AR1WasCreated? Devrait-il en être ainsi AR2WasCreated? De plus, si j'utilise votre logique, j'émets un événement AR2WasCreatedavant qu'il ne soit réellement créé? Et l'enregistrement de cet événement dans le journal des événements d'AR1 semble problématique, car je n'ai pas réellement besoin de ces données dans AR1 (cela ne modifie rien dans AR1).
Bojan Vukasovic
OK, 3 ans plus tard. Il va AR1WasCreated-> SAGA (a la règle si A1 a été créé puis créez A2) -> CreateAR2Command-> AR2WasCreated.
Bojan Vukasovic
@BojanVukasovic Je suis content que cela ait fonctionné comme je l'ai écrit :)
Constantin Galbenu
2

Comment créer de nouvelles racines agrégées dans l'architecture CQRS?

Les modèles de création sont étranges .

Udi Dahan a des choses utiles à dire sur le problème général: ne pas créer de racines agrégées . Le point fondamental étant que les agrégats ne sortent pas de nulle part et qu'il existe un langage de domaine qui décrit leur apparence, qui devrait être capturé dans votre modèle de domaine.

Là où cela a tendance à se tordre, c'est que l'entité de votre modèle de domaine qui traite la commande n'est pas l'entité modifiée par la transaction. Ce n'est pas faux; c'est juste bizarre (par rapport aux cas où vous demandez à une entité de se modifier.

Votre deuxième approche est également OK. Les «événements que nous déclenchons sans les enregistrer réellement dans la base de données» sont parfois appelés «événements de domaine»

L'idée de base étant que, dans la même transaction, le gestionnaire de commandes déclenche l'événement, qui se déplace le long du bus vers un gestionnaire d'événements qui permet au deuxième agrégat de se créer lui-même. Vous obtenez peut-être une meilleure cohésion du code.

Remarque: dans les systèmes basés sur des événements, vous n'utilisez généralement pas les événements de cette façon.

En cas d'utilisation du sourcing d'événements, ShouldCreateAR2Event ne serait pas conservé dans le magasin d'événements, car cela n'affecte pas l'état de la première racine agrégée.

Remarque: les noms d'événements sont généralement au passé - ShouldCrateAR2 a une mauvaise orthographe.

Oui, si vous lancez simplement un événement sur le bus synchrone pour exécuter le code à distance, vous ne devriez pas enregistrer cet événement dans le livre d'enregistrement. Ce n'est qu'un détail d'implémentation à cette échelle.

Ou devrions-nous toujours enregistrer cela dans le magasin d'événements?

Évitez de modifier deux flux d'événements différents dans la même transaction. Si cette création représente également un changement pour AR1, alors la réponse habituelle serait de modifier AR1 dans cette transaction, avec un abonné asynchrone à ces événements qui est responsable du déclenchement de la commande pour créer AR2.

La gestion idempotente des commandes aide beaucoup ici.

VoiceOfUnreason
la source
Merci de répondre. Encore une chose qui n'est pas claire à 100% - plus tard si je dois utiliser AR2 comme argument à AR1, comment dois-je passer cela - car CQRS indique que AR ne doit être utilisé que pour écrire et non pour interroger. Mais je n'ai pas d'autre option que d'utiliser AR1.doSmthn(AR2 param)car toute projection en lecture que je crée n'a pas de données complètes dont j'ai besoin (seul AR2 a des données complètes).
Bojan Vukasovic
> "Oui, si vous lancez simplement un événement sur le bus synchrone pour exécuter le code à distance, vous ne devriez pas enregistrer cet événement dans le livre des records." Je pense que la sauvegarde a une valeur réelle dans la mesure où vous savez que le processus a été lancé pour que quelque chose se produise, vous pouvez maintenant également suivre si cela a réellement été terminé. Mais je suppose que cela dépend du cas d'utilisation
Chaosekie