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).
Pour résumer, si la personne A retire de l'argent:
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:
==> 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:
Questions liées aux stratégies:
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
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:
Application layer
.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)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.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
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.