First of all, I have to agree with plalx's last paragraph regarding the need to synchronise the hidden flags in both Post
as well as Comment
. One would assume that the UI would look at a Post
and realise it's hidden and not bother fetching/showing the comments? But I appreciate that you may just be practicing DDD theories on a simple example.
I also agree with what he says regarding eventual consistency. However, for the point of this exercise I don't think its necessary to add the infrastructure required for it and a simpler approach can be taken.
I'd say there are two ways to do this, and the choice depends on how many comments each post is likely to have. Disclaimer: I'm a C# programmmer, so forgive me if the php syntax is wrong (I assume it is php?)
- Single aggregate design
If there aren't likely to be hundreds of comments per post, I would model Comment
as a child entity of Post
, where Post
is the only aggregate root. This way the hidden comment invariant is easy to enforce:
class Post {
private $hidden;
private $comments;
public function isHidden() {
return $this->hidden;
}
public function hide(){
$hidden = true;
foreach ($comments as $comment){
$comment.hide();
}
}
public function addComment($comment){
$comments.add($comment);
}
}
- Individual Aggregate Roots
If there are likely to be hundreds of comments being added to a post, then you'd need to model it as individual aggregates. Otherwise the Post aggregate will become too large, and perhaps more importantly (and as plalx points out) you would potentially get concurrency conflicts on the Post
aggregate where multiple comments are being added at the same time.
Doing it this way would involve using a Domain Service to handle the logic, rather than the caller using methods on the aggregates themselves:
class PostService {
private $postRepository;
private $commentRepository;
public function hidePost($postId) {
$post = $postRepository.GetById($postId);
$post.hide();
$postRepository.save($post);
//Method 1: update each comment
$comments = $commentRepository.GetCommentsByPostId($postId);
foreach($comments as $comment){
$comment.hide();
$commentRepository.save($comment);
}
//Method 2: create specific update method on repository with performant update query
$commentRepository.hideCommentsForPost($postId);
}
}
Note that the hide()
methods on the aggregate would not be available publicly. In C# these are called internal
methods, meaning only code in the same assembly can call them: the point being that callers are forced to use the PostService
to hide a post, not the $post.hide()
AR directly.
Also note, you should never reference another AR directly in a AR. You should reference other AR's by Id instead. See this for more info.