Reputation: 1958
Background
I have a system that is using CQRS + ES and in this system there are aggregates such as blog posts or issues that are persisted in the event store and send events over to the query side to persist the read model via projections.
In the case of a an issue or post being created it is a fairly straight forward
If a change is made to the issue and an appropriate event is raised on the write side such as IssueStatusChanged and handled on the read side accordingly. Load up the two de-normalised models on the read side update the status from the event and save. Easy.
How do you handle relations such as comments?
I am implementing a comment system where users can post comments on an issue or a blog post. My first thought was to have these comments added to the issue or post aggregate on the write side for consistency. When I thought about this though I realised that this would likely introduce a lot of unnecessary concurrency issues like when somebody is updating an issue and someone else has come and posted a new comment.
This led me to think that I should model comments themselves as their own aggregate root. This way comments posted to a blog post or issue will not cause conflicts with the issue itself.
So assuming I model comments on the write side as aggregates in this way, I have two questions;
1) Does the issue or post aggregate on the write side still need to store this relationship? The comment aggregate itself already stores which item it was posted too with an id reference.
If so, I was thinking of having the issue aggregate subscribe to the comment created event and add its own reference.
public class Issue : AggregateRoot, IEventHandler<CommentCreatedEvent>
{
private ICollection<Guid> _Comments;
public void Handle(CommentCreatedEvent @event)
{
_Comments.Add(@event.AggregateId)
}
}
Is this sufficient or not needed since the comment already stores a reference to its parent? This data isn't really needed on the write side and is more important on the read side when it is the parent that is loaded up with all comments.
2) On the read side what is the best way to store this data?
Specifically, in order to make this data easy to update I would need to put in another table for comments and join them to the appropriate post or issue. After I have finished with comments I will be implementing a following system where users can follow an item to receive updates. Going down this path however will very quickly lead me back to a highly normalised schema on the read side which defeats the purpose of an optimised, denormalised read model.
I was therefor thinking of adding a single column to the issue table for example that stored all comments as a serialised json clob or something. That way when changes come in to comments I can still pull out one record to load up the issue, make the appropriate changes to the comments (such as updating an existing comment, adding a new comment or removing one) and re-saving the record. From a read perspective, the entire issue can still be retrieved in one go.
The problem I see with this approach is that if a user changes their profile picture or profile name for example, I would have to load up every single issue and/or post, load up the comments and make the appropriate changes in the comment info.
I also wonder how document databases (something else I have been considering for the read side) get around this issue of updating nested data?
Upvotes: 2
Views: 3653
Reputation: 873
Question 1: You musn't handle events in your Aggregate root. It's a bad idea that breaks the DDD principles. If Comments lives in a different aggregate then any consequence in the Issue aggregate must be handled eventually by some kind of process manager, domain service or Saga in your domain.
If possible in your domain, you must states that Issue doesn't know about Comments (I guess is a natural way of thinking here), so you shouldn't keep any reference of that kind.
Comments on the other way can keep a reference to the issue they are related to.
Question 2: why don't you keep all the fields you need from Issue/post in your Comments table (handling the Issue/post updates)? This free you from joining between this two tables when querying your read model.
Upvotes: -1
Reputation: 16348
I'm a bit late at the party however, here's my take about no 2.
The best way to store the read model is in a way that it's very easy to query. A document db can be a good technical solution but it works with a rdbms as well, providing you have the relevant read model schema defined.
You can store all comments together with the post, however this is not always the case, because high traffic sites are loading comments separately from the posts via ajax. So it really depends on the read model use cases.
Upvotes: 2
Reputation: 7283
Question1: No need to have the relationships in Issue. No particular consistency to be protected here.
Question2: I'm recently reading NoSQL distilled. It seems Column-Family database like Casandra is suitable for the comments.
Row | issueId | name | comments |
| 1 | comments persistence solution | {c1,c2,c3} |
You could use Casandra api or Casandra query language to retrieve a subset of comments or the entire comments column.
UPDATE
is that comments column just a serialised collection of ids, the comments in their entirety? No the comments are stored as columns in a row. Casandra supports nested columns. so the comments column may have structure like this
| other columns | comments |
| ............ | c1 | c2 | c3 |
| "+1" | "Nice one" | "+1" |
You can get and set any comment alone in Casandra if I'm not mistaken. In this case, you can update any one comment. Or you can get the comments column to retrieve all comments.
Upvotes: 0