1
votes

Qui et comment doit gérer les événements de relecture?

J'apprends le DDD, le CQRS et le sourcing d'événements et il y a quelque chose que je n'arrive pas à comprendre. Les commandes déclenchent des modifications dans les agrégats et une fois la modification effectuée, un événement est déclenché. L'événement est ensuite géré par d'autres parties du système et conservé dans le magasin d'événements. Cependant, je ne comprends pas comment la relecture des événements recréerait l'agrégat, si les modifications sont déclenchées par des commandes.

Exemple: si nous avons une boutique en ligne. AddItemToCardCommand -> Card Aggregate ajoute l'élément à sa carte -> ItemAddedToCardEvent -> L'événement est géré par n'importe qui. Cependant, si l'événement est rejoué, l'agrégat n'ajoutera pas l'élément à sa carte.

Pour résumer, ma question est de savoir comment recréer des agrégats en fonction des événements dans le magasin d'événements? De plus, tout conseil général sur la façon de rejouer les événements de la bonne manière serait apprécié.


0 commentaires

3 Réponses :


1
votes

Par souci de simplicité, supposons un processus sans état - notre service n'essaie pas de conserver des copies des éléments en mémoire, mais recharge plutôt les agrégats si nécessaire.

Le service reçoit AddItemToCardCommand: {card: 123, ...} . Nous n'avons pas l'état actuel de la card: 123 en mémoire, nous devons donc le créer. Nous faisons cela en chargeant l'état de la carte: 123 à partir de notre magasin durable. Parce que nous avons choisi d'utiliser le stockage événementiel, «l'état» que nous lisons dans le magasin durable est une représentation de l'historique des événements précédemment écrits par le service.

Les historiques d'événements contiennent toutes les informations dont vous avez besoin de vous souvenir, mais pas nécessairement sous une «forme» pratique - les listes d'ajout uniquement sont une excellente structure de données pour les écritures, mais pas nécessairement pour les lectures.

Cela signifie souvent que nous «rejouerons» les événements pour créer un objet en mémoire que nous pourrons ensuite utiliser pour répondre aux questions sur les événements que nous écrirons ensuite.

Le même modèle est utilisé pour répondre à des requêtes simples: nous chargeons l'historique des événements à partir du magasin, transformons l'historique des événements en une forme plus pratique, puis utilisons cette forme pour calculer la réponse.

Dans les cas où la latence des requêtes est plus importante que la rapidité, nous pouvons concevoir notre gestionnaire de requêtes pour lire les formes pratiques à partir d'un cache, plutôt que d'essayer de les calculer à chaque fois; un thread d'arrière-plan fonctionnant simultanément serait chargé de se réveiller périodiquement pour calculer de nouveaux contenus pour le cache.

L'utilisation d'un processus asynchrone pour extraire les mises à jour d'un flux d'événements est un modèle courant; Greg Young discute de certains des avantages de cette approche dans son exposé sur Polyglot Data .


0 commentaires

1
votes

Dans un scénario d'événement idéal, vous n'auriez pas une structure d'agrégation déjà construite disponible dans votre base de données. Vous arrivez à plusieurs reprises à la structure de données finale en parcourant tous les événements stockés jusqu'à présent.

Permettez-moi d'illustrer avec un pseudocode d'ajout d'articles au panier, puis de récupérer les données du panier.

cart = Cart(id=ID1)

# Fetch contents of Cart with id ID1
for each event in ID1 cart's events:
    if event is ItemAddedToCart:
        cart.add_item(event.data)
    else if event is ItemRemovedFromCart:
        cart.remove_item(event.data)

return cart


0 commentaires

0
votes

Ce qui peut aider, c'est de ne pas considérer la commande comme un changement d'état mais plutôt l ' événement comme un changement d'état. En fait, je ne vois pas très bien comment procéder autrement. Le gestionnaire de commandes de votre agrégat appliquerait les invariants et, si tout va bien, créerait immédiatement l'événement et appellerait une méthode qui l'appliquerait ( [Apply | On | Do] MyEvent ). Le fait que vous ayez un événement après coup ne signifie pas nécessairement que d’autres parties de votre système le géreraient. Il est cependant requis pour la recherche d'événements. Une fois que vous avez un événement, vous pouvez très certainement le transmettre à d'autres parties de votre système via, par exemple, la publication sur un bus de service.

Lorsque vous rejouez vos événements, vous appelez les mêmes méthodes que les commandes appelaient pour muter. l'état de votre agrégat:

public MyEvent MyCommand(string data)
{
    if (string.IsNullOrWhiteSpace(data))
    {
        throw new ArgumentException($"Argument '{nameof(data)}' may not be empty.");
    }

    return On(new MyEvent
    {
        Data = data
    });
}

private MyEvent On(MyEvent myEvent)
{
    // change the relevant state
    someState = myEvent.Data;

    return myEvent;
}

Votre infrastructure de recherche d'événements appellerait On (MyEvent) pour MyEvent lors de la relecture. Puisque vous avez un événement , cela signifie qu'il était une transition d'état valide et peut simplement être appliqué; Sinon, quelque chose s'est mal passé dans le traitement de votre commande initial et vous avez probablement un bogue.

Tous les événements d'un magasin d'événements seraient dans l'ordre chronologique pour un agrégat. En plus de cela, les événements doivent avoir un numéro de séquence global pour faciliter le traitement de la projection.

Vous pouvez avoir une projection générique qui accepte tout / tous les événements, puis publie l'événement sur un bus de service pour l'intégration du système. Vous pouvez également placer ce fardeau sur un client du magasin d'événements pour qu'il garde une trace de la position elle-même, puis lise les événements sur le magasin lui-même. Vous pouvez les combiner et demander au client de s'abonner aux événements du bus de service, mais assurez-vous qu'il les exécute dans le même ordre en gardant une trace de la position (numéro de séquence global) elle-même et en la mettant à jour au fur et à mesure que les événements sont traités.

p>


2 commentaires

Mais les événements ne sont-ils pas censés dire ce qui s'est passé? Si l'événement est déclenché et que l'état est changé, alors l'événement dit ce qui va se passer et dans ce cas, pourquoi prendrions-nous même la peine d'envoyer une commande au moment de simplement déclencher l'événement?


Les événements font dire ce qui s’est passé, c’est pourquoi nous pouvons faire muter l’état en toute sécurité. Par exemple, si votre Account.Balance est 0 et que vous avez un événement Deposited pour 100 USD, vous pouvez modifier en toute sécurité le solde à 100 USD en ajoutant le montant. Maintenant, si une commande Retrait est émise pour 200 $ et qu'aucun découvert n'a été enregistré, alors cette commande est refusée. Mais si nous avons un événement Retiré pour 25 $, nous pouvons en toute sécurité changer le solde à 75 $ en déduisant le montant. Rejouer les événements nous laisserait avec un agrégat dans un état correct de la même manière que les transactions de compte modifient les soldes.