As far as I understand, the big idea behind CQRS is having 2 different data models for handling commands and queries. These are called "write model" and "read model".
Let's consider an example of Twitter application clone. Here are the commands:
- Users can register themselves.
CreateUserCommand(string username)
emitsUserCreatedEvent
- Users can follow other users.
FollowUserCommand(int userAId, int userBId)
emitsUserFollowedEvent
- Users can create posts.
CreatePostCommand(int userId, string text)
emitsPostCreatedEvent
While I use the term "event" above, I don't mean 'event sourcing' events. I just mean signals that trigger read model updates. I don't have an event store and so far want to concentrate on CQRS itself.
And here are the queries:
- A user needs to see the list of its posts.
GetPostsQuery(int userId)
- A user needs to see the list of its followers.
GetFollowersQuery(int userId)
- A user needs to see the list of users it follows.
GetFollowedUsersQuery(int userId)
- A user needs to see the "friend feed" - a log of all their friends' activities ("your friend John has just created a new post").
GetFriedFeedRecordsQuery(int userId)
To handle CreateUserCommand
I need to know if such a user already exists. So, at this point I know that my write model should have a list of all users.
To handle FollowUserCommand
I need to know if userA already follows userB or not. At this point I want my write model to have a list of all user-follows-user connections.
And finally, to handle CreatePostCommand
I don't think I need anything else, because I don't have commands like UpdatePostCommand
. If I had those, I would need to make sure that post exists, so I would need a list of all posts. But because I don't have this requirement, I don't need to track all posts.
Question #1: is it actually correct to use the term "write model" they way I use it? Or does "write model" always stand for "event store" in case of ES? If so, is there any kind of separation between the data I need to handle commands and the data I need to handle queries?
To handle GetPostsQuery
, I would need a list of all posts. This means that my read model should have a list of all posts. I'm going to maintain this model by listening to PostCreatedEvent
.
To handle both GetFollowersQuery
and GetFollowedUsersQuery
, I would need a list of all connections between users. To maintain this model I'm going to listen to UserFollowedEvent
. Here is a Question #2: is it practically OK if I use write model's list of connections here? Or should I better create a separate read model, because in the future I may need it to have more details than write model has?
Finally, to handle GetFriendFeedRecordsQuery
I would need to:
- Listen to
UserFollowedEvent
- Listen to
PostCreatedEvent
- Know which users follow which other users
If user A follows user B and user B starts to follow user C, the following records should appear:
- For user A: "You friend user B has just started following user C"
- For user B: "You've just started following user C"
- For user C: "User B is now following you"
Here's the Question #3: what model should I use to get the list of connections? Should I use write model? Should I use read model - GetFollowersQuery
/GetFollowedUsersQuery
? Or should I make GetFriendFeedRecordsQuery
model itself handle the UserFollowedEvent
and maintain its own list of all connections?