web-dev-qa-db-fra.com

Gestion des accès simultanés ES / CQRS

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 délai aussi court est impossible, mais j'ai pris un exemple simple, pour ne pas se perdre dans les détails

21
Louis F.

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 une commande est traitée ou relancé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 Aggregate et le charge à partir du référentiel (dans ce cas, le chargement est effectué par new- une instance Aggregate, récupérant tous les événements précédemment émis de cet agrégat et les réappliquer à l'agrégat lui-même; la version d'agrégat est stockée pour une 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 Aggregate, comme Account::withdrawMoney(100) et collecte les événements générés, 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 Aggregate dans 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 stream si et seulement si le version du Aggregate est toujours celui qui était quand le Aggregate était chargé. Si la version n'est pas la même, alors la commande est relancée - passez à l'étape 1. Si le version est le même, les événements sont ajoutés au Event stream Et le client reçoit le statut Success.

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 stream Est une abstraction autour de tous les événements qui ont été émis par le même agrégat.

Vous devez comprendre que le Event store N'est qu'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

22

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).

Proche. 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 ceci sont le cas habituel - plutôt que en ajoutant au flux dans le flux d'événements, nous avons généralement METTONS à un spécifique emplacement dans le ruisseau; 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.

1
VoiceOfUnreason