Pour autant que je sache, la grande idée derrière CQRS est d'avoir 2 modèles de données différents pour gérer les commandes et les requêtes. Ceux-ci sont appelés "modèle d'écriture" et "modèle de lecture".
Prenons un exemple de clone d'application Twitter. Voici les commandes:
- Les utilisateurs peuvent s'inscrire.
CreateUserCommand(string username)
émetUserCreatedEvent
- Les utilisateurs peuvent suivre les autres utilisateurs.
FollowUserCommand(int userAId, int userBId)
émetUserFollowedEvent
- Les utilisateurs peuvent créer des publications.
CreatePostCommand(int userId, string text)
émetPostCreatedEvent
Bien que j'utilise le terme «événement» ci-dessus, je ne parle pas des événements de «sourçage d'événements». Je veux juste dire des signaux qui déclenchent des mises à jour du modèle de lecture. Je n'ai pas de magasin d'événements et jusqu'à présent, je veux me concentrer sur le CQRS lui-même.
Et voici les requêtes:
- Un utilisateur doit voir la liste de ses publications.
GetPostsQuery(int userId)
- Un utilisateur doit voir la liste de ses abonnés.
GetFollowersQuery(int userId)
- Un utilisateur doit voir la liste des utilisateurs qu'il suit.
GetFollowedUsersQuery(int userId)
- Un utilisateur a besoin de voir le "flux d'amis" - un journal de toutes les activités de ses amis ("votre ami John vient de créer un nouveau message").
GetFriedFeedRecordsQuery(int userId)
Pour gérer, CreateUserCommand
j'ai besoin de savoir si un tel utilisateur existe déjà. Donc, à ce stade, je sais que mon modèle d'écriture devrait avoir une liste de tous les utilisateurs.
Pour gérer, FollowUserCommand
j'ai besoin de savoir si userA suit déjà userB ou non. À ce stade, je veux que mon modèle d'écriture ait une liste de toutes les connexions utilisateur-utilisateur-utilisateur.
Et enfin, pour gérer, CreatePostCommand
je ne pense pas avoir besoin d'autre chose, car je n'ai pas de commandes comme UpdatePostCommand
. Si je les avais, je devrais m'assurer que le message existe, donc j'aurais besoin d'une liste de tous les messages. Mais parce que je n'ai pas cette exigence, je n'ai pas besoin de suivre tous les messages.
Question n ° 1 : est-il vraiment correct d'utiliser le terme "modèle d'écriture" comme je l'utilise? Ou "écrire le modèle" signifie-t-il toujours "magasin d'événements" en cas d'ES? Dans l'affirmative, existe-t-il une sorte de séparation entre les données dont j'ai besoin pour gérer les commandes et les données dont j'ai besoin pour gérer les requêtes?
Pour gérer GetPostsQuery
, j'aurais besoin d'une liste de tous les messages. Cela signifie que mon modèle de lecture devrait avoir une liste de tous les messages. Je vais maintenir ce modèle en écoutant PostCreatedEvent
.
Pour gérer les deux GetFollowersQuery
et GetFollowedUsersQuery
, j'aurais besoin d'une liste de toutes les connexions entre les utilisateurs. Pour maintenir ce modèle, je vais écouter UserFollowedEvent
. Voici une question n ° 2 : est-ce pratiquement OK si j'utilise ici la liste des connexions du modèle d'écriture? Ou devrais-je mieux créer un modèle de lecture distinct, car à l'avenir, j'aurai peut-être besoin de plus de détails que le modèle d'écriture?
Enfin, pour gérer, GetFriendFeedRecordsQuery
je devrais:
- Ecouter
UserFollowedEvent
- Ecouter
PostCreatedEvent
- Savoir quels utilisateurs suivent quels autres utilisateurs
Si l'utilisateur A suit l'utilisateur B et que l'utilisateur B commence à suivre l'utilisateur C, les enregistrements suivants doivent apparaître:
- Pour l'utilisateur A: "Votre ami utilisateur B vient de commencer à suivre l'utilisateur C"
- Pour l'utilisateur B: "Vous venez de commencer à suivre l'utilisateur C"
- Pour l'utilisateur C: "L'utilisateur B vous suit maintenant"
Voici la question n ° 3 : quel modèle dois-je utiliser pour obtenir la liste des connexions? Dois-je utiliser le modèle d'écriture? Dois-je utiliser le modèle de lecture - GetFollowersQuery
/ GetFollowedUsersQuery
? Ou dois-je faire en sorte que le GetFriendFeedRecordsQuery
modèle gère lui-même la UserFollowedEvent
liste de toutes les connexions et la maintienne?
la source
Réponses:
Greg Young (2010)
Si vous pensez en termes de séparation des requêtes de commande de Bertrand Meyer , vous pouvez considérer le modèle comme ayant deux interfaces distinctes, une qui prend en charge les commandes et une qui prend en charge les requêtes.
La perspicacité de Greg Young était que vous pouviez séparer cela en deux objets distincts
Après avoir séparé les objets, vous avez maintenant la possibilité de séparer les structures de données qui contiennent l'état de l'objet en mémoire, afin que vous puissiez optimiser pour chaque cas; ou stocker / conserver l'état de lecture séparément de l'état d'écriture.
Le terme WriteModel est généralement compris comme étant la représentation modifiable du modèle (c'est-à-dire: l'objet, pas le magasin de persistance).
TL; DR: Je pense que votre utilisation est bonne.
C'est "bien" - ish. Sur le plan conceptuel, il n'y a rien de mal à ce que le modèle de lecture et le modèle d'écriture partagent les mêmes structures.
En pratique, étant donné que les écritures sur le modèle ne sont généralement pas atomiques, il peut y avoir un problème lorsqu'un thread tente de modifier l'état du modèle tandis qu'un deuxième thread tente de le lire.
L'utilisation du modèle d'écriture n'est pas la bonne réponse.
Il est tout à fait raisonnable d'écrire plusieurs modèles de lecture, où chacun est réglé sur un cas d'utilisation particulier. Nous disons "le modèle de lecture", mais il est entendu qu'il pourrait y avoir de nombreux modèles de lecture, dont chacun est optimisé pour un cas d'utilisation particulier, et ne met pas en œuvre les cas où cela n'a pas de sens.
Par exemple, vous pouvez décider d'utiliser un magasin de valeurs-clés pour prendre en charge certaines requêtes, et une base de données graphique pour d'autres requêtes, ou une base de données relationnelle où ce modèle de requête a du sens. Chevaux de course.
Dans votre cas particulier, où vous apprenez encore le modèle, ma suggestion serait de garder votre conception "simple" - avoir un modèle de lecture qui ne partage pas la structure de données du modèle d'écriture.
la source
Je vous encourage à considérer que vous avez un modèle de données conceptuel.
Ensuite, le modèle d'écriture est une matérialisation de ce modèle de données qui est optimisé pour les mises à jour transactionnelles. Parfois, cela signifie une base de données relationnelle normalisée.
Le modèle de lecture est une matérialisation de ce même modèle de données optimisé pour effectuer les requêtes dont vos applications ont besoin. Il peut toujours s'agir d'une base de données relationnelle, bien que dénormalisée à dessein pour gérer les requêtes avec moins de jointures.
(# 1) Pour CQRS, le modèle d'écriture ne doit pas nécessairement être un magasin d'événements.
(# 2) Je ne m'attendrais pas à ce que le modèle de lecture stocke tout ce qui ne se trouve pas dans le modèle d'écriture, notamment parce que dans CQRS, personne ne met à jour le modèle de lecture, sauf le mécanisme de transfert qui maintient le modèle de lecture synchronisé avec les modifications apportées à la écrire le modèle.
(# 3) Dans CQRS, les requêtes doivent aller à l'encontre du modèle de lecture. Vous pouvez faire différemment: c'est bon, mais ne suivez pas CQRS.
En résumé, il n'y a qu'un seul modèle de données conceptuel. CQRS sépare le réseau et les capacités de commande du réseau et des capacités de requête. Étant donné que la séparation, le modèle d'écriture et le modèle de lecture peuvent être hébergés en utilisant des technologies très différentes comme optimisation des performances.
la source