天行健君子以自强

天行健君子以自强

前言

聚合作为领域模型中重要的业务功能单元,它的设计是领域建模过程中非常重要的工作。其中聚合根的判断并非一件易事,往往给人一种似是而非的感觉,让人难以捉摸,陷入两难的境地。今天笔者就想以博客园为例来探讨下:博客 (Blog) 和评论 (Comment) 究竟是不是一个聚合?

问题探讨

众所周知,在博客这个领域中,核心子域就是写博客。从博客这个限界上下文中,我们很容易提炼出博客和评论两个领域对象,两者之间是一种从属关系。那么我们该如何来进行聚合设计呢?先来回顾下DDD中聚合的概念:

我想很多人心中已有答案:博客和评论不就是一个具有从属关系的聚合吗?所有评论都是围绕博客而存在的,一旦博客被删除后,那么评论也将不复存在。当我们要查看或发表评论,我们必须先找到自己感兴趣的博客才行。试想,现实中存不存在这样的业务场景,可以绕开博客直接查看或发表评论? 答案是否。因此在博客这个聚合里,博客就是聚合根,而评论就是实体。如果仅靠从属关系来判定聚合的话,笔者认为依据是不充分的,继续往下看。

现在我们再来思考:博客和评论除了从属关系之外,两者之间还存在哪些约束关系?根据博客园现有的功能,我们可以得出以下业务规则:每个用户都可以发表评论,但只能修改和删除自己的评论,只有博主可以删除别人评论。最终转换成的业务代码,大致如下:

    /// <summary>
    /// 博客
    /// </summary>
    public class Blog : IAggregateRoot
    {
        /// <summary>
        /// 博客Id
        /// </summary>
        public Guid Id { get; private set; }

        /// <summary>
        /// 博主Id
        /// </summary>
        public Guid OwnerId { get; private set; }

        /// <summary>
        /// 评论集合
        /// </summary>
        public ICollection<Comment> Comments { get; private set; }

        /// <summary>
        /// 添加评论
        /// </summary>
        /// <param name="commentId">评论Id</param>
        /// <param name="commentId">评论内容</param>
        /// <param name="userId">用户Id</param>
        public void AddComment(Guid commentId, string content, Guid userId)
        {
            var comment = new Comment(commentId, content, Id, userId);
            Comments.Add(comment);
        }

        /// <summary>
        /// 删除评论
        /// </summary>
        /// <param name="commentId">评论Id</param>
        /// <param name="userId">用户Id</param>
        public void RemoveComment(Guid commentId, Guid userId)
        {
            var comment = Comments.Single(c => c.Id == commentId);
            if (comment.OwnerId != userId && OwnerId != userId)
            {
                throw new UserFriendlyException("不能删除别人的评论");
            }
            Comments.Remove(comment);
        }

        /// <summary>
        /// 修改评论
        /// </summary>
        /// <param name="commentId">评论Id</param>
        /// <param name="content">评论内容</param>
        /// <param name="userId">用户Id</param>
        public void UpdateComment(Guid commentId, string content, Guid userId)
        {
            var comment = Comments.Single(c => c.Id == commentId);
            if (comment.OwnerId != userId)
            {
                throw new UserFriendlyException("不能修改别人的评论");
            }
            comment.Content = content;
        }
    }
09-02 02:26