Gestion des accès simultanés ES / CQRS

20

J'ai récemment commencé à plonger dans CQRS / ES parce que je pourrais avoir besoin de l'appliquer au travail. Cela semble très prometteur dans notre cas, car cela résoudrait beaucoup de problèmes.

J'ai esquissé ma compréhension approximative de l'apparence contextuelle d'une application ES / CQRS dans un cas d'utilisation bancaire simplifié (retrait d'argent).

ES / CQRS

Pour résumer, si la personne A retire de l'argent:

  • une commande est émise
  • la commande est remise pour validation / vérification
  • un événement est poussé vers un magasin d'événements si la validation réussit
  • un agrégateur retire l'événement pour appliquer des modifications à l'agrégat

D'après ce que j'ai compris, le journal des événements est la source de la vérité, comme c'est le journal des FAITS, nous pouvons alors en tirer toute projection.


Maintenant, ce que je ne comprends pas, dans ce grand schéma des choses, c'est ce qui se passe dans ce cas:

  • règle: un solde ne peut pas être négatif
  • la personne A a un solde de 100e
  • la personne A émet une commande de retrait de 100e
  • passe de validation et l'événement MoneyWithdrewEvent of 100e est émis
  • en attendant, la personne A émet une autre commande de retrait de 100e
  • le premier MoneyWithdrewEvent n'a pas encore été agrégé, donc la validation passe, car la vérification de validation par rapport à l'agrégat (qui n'a pas encore été mise à jour)
  • MoneyWithdrewEvent of 100e est émis une autre fois

==> Nous sommes dans un état incohérent d'un solde étant à -100e et le journal contient 2 MoneyWithdrewEvent

Si je comprends bien, il existe plusieurs stratégies pour faire face à ce problème:

  • a) mettre l'id de version agrégée avec l'événement dans le magasin d'événements, donc s'il y a une incompatibilité de version lors de la modification, rien ne se passe
  • b) utiliser certaines stratégies de verrouillage, ce qui implique que la couche de vérification doit en créer une

Questions liées aux stratégies:

  • a) Dans ce cas, le journal des événements n'est plus la source de la vérité, comment y faire face? De plus, nous sommes revenus au client OK alors que c'était totalement faux de permettre le retrait, est-il préférable dans ce cas d'utiliser des serrures?
  • b) Locks == deadlocks, avez-vous une idée des meilleures pratiques?

Dans l'ensemble, ma compréhension est-elle correcte sur la façon de gérer la concurrence?

Remarque: je comprends que la même personne qui retire deux fois de l'argent dans un laps de temps aussi court est impossible, mais j'ai pris un exemple simple, pour ne pas se perdre dans les détails

Louis F.
la source
Pourquoi ne pas mettre à jour l'agrégat à l'étape 4 au lieu d'attendre l'étape 7?
Erik Eidt
Donc, vous voulez dire que dans ce cas, le magasin d'événements n'est qu'un journal qui n'est lu qu'au démarrage de l'application pour recréer des agrégats / autres projections?
Louis F.

Réponses:

19

J'ai esquissé ma compréhension approximative de l'apparence contextuelle d'une application ES / CQRS dans un cas d'utilisation bancaire simplifié (retrait d'argent).

Ceci est l'exemple parfait d'une application issue d'un événement. Commençons.

Chaque fois qu'une commande est traitée ou réessayée (vous comprendrez, soyez patient), les étapes suivantes sont effectuées:

  1. la commande atteint un gestionnaire de commandes, c'est-à-dire un service dans le Application layer.
  2. le gestionnaire de commandes identifie le Aggregateet le charge à partir du référentiel (dans ce cas, le chargement est effectué en chargeant newune Aggregateinstance, en récupérant tous les événements précédemment émis de cet agrégat et en les réappliquant à l'agrégat lui-même; la version de l'agrégat est stockée pour utilisation ultérieure; après l'application des événements, l'agrégat est dans son état final - c'est-à-dire que le solde du compte courant est calculé sous forme de nombre)
  3. le gestionnaire de commandes appelle la méthode appropriée sur le Aggregate, like Account::withdrawMoney(100)et collecte les événements produits, c'est-à-dire MoneyWithdrewEvent(AccountId, 100); s'il n'y a pas assez d'argent dans le compte (solde <100) alors une exception est levée et tout est annulé; sinon, l'étape suivante est effectuée.
  4. le gestionnaire de commandes essaie de conserver le Aggregatedans le référentiel (dans ce cas, le référentiel est le Event Store); il le fait en ajoutant les nouveaux événements au Event streamsi et seulement si le versiondu Aggregateest toujours celui qui était lorsque le a Aggregateété chargé. Si la version n'est pas la même, la commande est réessayée - passez à l'étape 1 . Si le versionest le même, les événements sont ajoutés à Event streamet le client reçoit le Successstatut.

Cette vérification de version est appelée verrouillage optimiste et est un mécanisme de verrouillage général. Un autre mécanisme est le verrouillage pessimiste lorsque d'autres écritures sont bloquées (comme non démarrées) jusqu'à ce que celle en cours soit terminée.

Le terme Event streamest une abstraction autour de tous les événements qui ont été émis par le même agrégat.

Vous devez comprendre que Event storec'est juste un autre type de persistance où sont stockées toutes les modifications apportées à un agrégat, pas seulement l'état final.

a) Dans ce cas, le journal des événements n'est plus la source de la vérité, comment y faire face? De plus, nous sommes revenus au client OK alors que c'était totalement faux de permettre le retrait, est-il préférable dans ce cas d'utiliser des serrures?

Le magasin d'événements est toujours la source de la vérité.

b) Locks == deadlocks, avez-vous une idée des meilleures pratiques?

En utilisant un verrouillage optimiste, vous n'avez aucun verrou, il suffit de réessayer la commande.

Quoi qu'il en soit, verrous! = Deadlocks

Constantin Galbenu
la source
2
Il y a quelques optimisations concernant le chargement d'un Aggregateoù vous n'appliquez pas tous les événements mais vous gardez un instantané du Aggregatejusqu'à un certain point dans le passé et n'appliquez que les événements qui se sont produits après ce point.
Constantin Galbenu
Ok, je pense que ma confusion vient du fait que le magasin d'événements == bus d'événements (j'ai Kafka en tête), donc la reconstruction de l'agrégat pourrait être coûteuse car vous pourriez avoir besoin de relire BEAUCOUP d'événements. Dans le cas d'un instantané de la Aggregate, quand l'instantané doit-il être mis à jour? Le magasin d'instantanés est-il le même que le magasin d'événements ou s'agit-il d'une vue matérialisée dérivée du bus d'événements?
Louis F.
Il existe certaines stratégies pour créer l'instantané. L'une consiste à créer un instantané tous les n événements. Vous devez stocker l'instantané avec les événements, au même endroit / persistance / base de données, sur le même commit. L'idée est que l'instantané est fortement lié à la version de l'agrégat.
Constantin Galbenu
Ok, je pense que j'ai une vision plus claire sur la façon de gérer cela. Maintenant dernière question, quel est le rôle du bus d'événement à la fin? Si les agrégats sont mis à jour de manière synchrone?
Louis F.
1
Oui, vous pouvez utiliser un RabbitMQ ou n'importe quel canal pour envoyer les événements aux modèles lus de manière asynchrone, mais uniquement après les avoir conservés dans le magasin d'événements. En effet, aucune validation d'événement n'est effectuée après leur persistance: les événements représentent des faits qui se sont produits; un modèle de lecture peut ou non aimer que quelque chose se soit produit mais il ne peut pas changer l'historique.
Constantin Galbenu
1

J'ai esquissé ma compréhension approximative de l'apparence contextuelle d'une application ES / CQRS dans un cas d'utilisation bancaire simplifié (retrait d'argent).

Fermer. Le problème est que la logique de mise à jour de votre "agrégat" est dans un endroit étrange.

L'implémentation la plus courante est que le modèle de données que votre gestionnaire de commandes conserve en mémoire et le flux d'événements dans le magasin d'événements sont synchronisés.

Un exemple simple à décrire est le cas où le gestionnaire de commandes effectue des écritures synchrones dans le magasin d'événements et met à jour sa copie locale du modèle si la connexion au magasin d'événements indique que l'écriture a réussi.

Si le gestionnaire de commandes doit se resynchroniser avec le magasin d'événements (car son modèle interne ne correspond pas à celui du magasin), il le fait en chargeant l'historique à partir du magasin et en reconstruisant son propre état interne.

En d'autres termes, les flèches 2 et 3 (si présentes) seraient normalement connectées au magasin d'événements, et non à un magasin agrégé.

mettre l'ID de version agrégée avec l'événement dans le magasin d'événements, donc s'il y a une incompatibilité de version lors de la modification, rien ne se passe

Les variations de ce sont habituellement le cas - plutôt que annexant au courant dans le flux d'événements, nous avons généralement METTONS à un emplacement spécifique dans le cours d' eau; si cette opération est incompatible avec l'état du magasin, l'écriture échoue et le service peut choisir le mode d'échec approprié (échec au client, réessayer, fusionner ....). L'utilisation d'écritures idempotentes résout un certain nombre de problèmes dans la messagerie distribuée, mais bien sûr, cela nécessite d'avoir un magasin qui prend en charge une écriture idempotente.

VoiceOfUnreason
la source
Hmm, je pense que j'ai alors mal compris le composant du magasin d'événements. Je pensais que tout devrait passer par là et être diffusé. Que faire si mon magasin d'événements est un kafka et est en lecture seule? Je ne peux pas me permettre à l'étape 2 et 3 de parcourir à nouveau tous les messages. Il semble que dans l'ensemble ma vision corresponde à celle-ci: medium.com/technology-learning/…
Louis F.