J'essaie d'apprendre des façons de DDD et des sujets connexes. Je suis venu avec une idée de contexte borné simple pour mettre en œuvre la «banque»: il y a des comptes, l'argent peut être déposé, retiré et transféré entre eux. Il est également important de conserver l'historique des modifications.
J'ai identifié une entité de compte et cette source d'événements serait utile pour garder une trace de ses modifications. D'autres entités ou objets de valeur ne sont pas pertinents pour le problème, donc je ne les mentionnerai pas.
Lorsque l'on considère les dépôts et les retraits - c'est relativement simple, car il n'y a qu'un seul agrégat modifié.
Lors du transfert, c'est différent - deux agrégats doivent être modifiés par un événement MoneyTransferred . DDD déconseille de modifier plusieurs agrégats en une seule transaction. D'autre part, la règle du sourcing d'événements consiste à appliquer des événements aux entités et à modifier l'état en fonction de celles-ci. Si l'événement pouvait être stocké simplement dans la base de données, il n'y aurait aucun problème. Mais pour empêcher la modification simultanée des entités issues d'événements, nous devons implémenter quelque chose de versionner le flux d'événements de chaque agrégat (pour conserver leurs limites de transaction). Avec le versionnage vient un autre problème - je ne peux pas utiliser de structures simples pour stocker des événements et les relire pour les appliquer à l'agrégation.
Ma question est la suivante: comment puis-je réunir ces trois principes: "un agrégat une transaction", "événement-> changement d'agrégat" et "prévention des modifications simultanées"?
la source
Un détail important pour comprendre les comptes basés sur les transactions: l'
balance
attribut deaccount
est en fait une instance de dénormalisation. C'est là pour plus de commodité. En réalité, le solde d'un compte est la somme de ses transactions, et vous n'avez pas vraiment besoin du compte lui-même pour avoir un solde.Gardant cela à l'esprit, l'acte de transférer un argent ne devrait pas être de mettre à jour
account
mais d'insérertransaction
.Cela étant dit, il existe une autre règle importante: l'acte d'ajouter un
transaction
devrait être atomique avec une mise à jour du (champ d'équilibre dénormalisé de)account
.Maintenant, si je comprends le concept DDD d'agrégats, ce qui suit semble pertinent:
Donc, en termes de conception DDD, je suggère:
Il y a un agrégat pour représenter le transfert
L'agrégat est composé des objets suivants: le transfert (l'objet racine); l'objet racine est lié à deux listes de transactions (une pour chaque compte); et chaque liste de transactions est liée à un compte.
Tout accès au transfert doit être médité par l'objet racine (le
transfer
).Si vous essayez d'implémenter la prise en charge du transfert asynchrone, votre code principal devrait simplement s'inquiéter de la création du transfert, dans un état "en attente". Vous pouvez avoir un autre thread ou un travail qui déplace réellement l'argent (l'insertion dans l'historique des transactions, et donc la mise à jour des soldes) et définit le transfert sur "publié".
Si vous cherchez à implémenter une transaction de transfert bloquante en temps réel, la logique métier doit créer un
transfer
et cet objet coordonnerait les autres activités en temps réel.En termes de prévention des problèmes de concurrence, le premier ordre du jour devrait être d'insérer la transaction de débit dans la liste des transactions pour le compte source (mise à jour du solde, bien sûr). Cela devrait être effectué de manière atomique au niveau de la base de données (via une procédure stockée). Une fois le débit effectué, le reste du transfert devrait pouvoir réussir indépendamment des problèmes de concurrence, car aucune règle commerciale ne devrait empêcher un crédit sur le compte cible.
(Dans le monde réel, les comptes bancaires ont le concept d'un message mémo qui prend en charge un concept de validation en deux phases paresseux. La création du message mémo est légère et facile, et elle peut également être annulée sans problème. Conversion du La publication d'un mémo dans un message dur est le moment où l'argent se déplace réellement - cela ne peut pas être annulé - et représente la deuxième phase de la validation en deux phases, qui ne se produit qu'après que toutes les règles de validation ont été vérifiées).
la source
Je suis également actuellement en phase d'apprentissage. Du point de vue de la mise en œuvre, voici comment je pense que vous allez effectuer cette action.
Dispatch TransferMoneyCommand qui déclenche les événements suivants [MoneyTransferEvent, AccountDebitedEvent]
Notez avant qu'il ne déclenche ces événements, une validation de commande superficielle et une validation de logique de domaine devront être effectuées, c'est-à-dire que le compte a-t-il un solde suffisant?
Conservez les événements (avec versioning) pour vous assurer qu'il n'y a pas de problèmes de cohérence. Notez qu'il pourrait y avoir une autre commande simultanée (comme retirer tout l'argent) qui a réussi et enregistré des événements avant celui-ci, donc l'état actuel de l'agrégat peut être obsolète et donc les événements sont déclenchés sur l'ancien état et sont incorrects. Si la sauvegarde des événements échoue, vous devrez réessayer la commande depuis le début.
Une fois les événements enregistrés avec succès dans la base de données, vous pouvez publier les deux événements qui ont été déclenchés.
AccountDebitedEvent supprimera l'argent du compte du payeur (met à jour l'état global et tous les modèles de vue / projection associés)
MoneyTransferEvent démarre Saga / Process Manager.
Le travail du responsable de la saga / du processus sera d'essayer de créditer le compte du bénéficiaire, s'il échoue, il devra créditer le solde au payeur.
Le responsable de la saga / processus publiera un CreditAccountCommand qui est appliqué au compte du bénéficiaire et en cas de succès, AccountCreditedEvent sera généré.
Du point de vue de la recherche d'événements, si vous souhaitez annuler cette action, tous les événements de cette transaction auront l'ID de corrélation / causalité comme TransferMoneyCommand d'origine que vous pouvez utiliser pour déclencher des événements pour les opérations d'annulation / d'annulation.
N'hésitez pas à suggérer des problèmes ou des améliorations potentielles sur ce qui précède.
la source