gpt4 book ai didi

关于DDD中聚合设计的思考(以博客园为例)

转载 作者:我是一只小鸟 更新时间:2023-08-31 07:38:29 31 4
gpt4 key购买 nike

前言

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

问题探讨

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

聚合(Aggregate)定义了一组具有内聚关系的相关对象的集合,我们把聚合看作是一个修改数据的单元。每个聚合都含有一个根实体,叫做聚合根(Aggregate Root),它是聚合的管理者.

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

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

                          
                            ///
                          
                          
                            <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;
        }
                          
                          
                            
    }
                          
                        

从上述代码中,细心的网友不难发现:博客和评论之间维持的是一种很弱的业务关系,而且每次增加、删除或修改评论,都必须先把评论全部检索出来(因为聚合是一个完整的数据单元)。肯定会有人质疑:这样做是否有必要?如果评论很多的话,对查询性能没有影响吗?这是笔者曾经看到过的一篇博文《 博客园的大牛们,被你们害惨了,Entity Framework从来都不需要去写Repository设计模式 》,评论数多达291条,从性能角度而言,这的确是一件令人担忧的事情。于是,我们就该反思领域建模哪里出了问题?

我们再来看评论这个领域对象,它本身是一个实体,且含有特定的业务规则:即每个用户只能对别人的评论发表支持/反对意见,这样才能客观公正反映评论的价值。因此在评论这个领域中,评论和评论意见就是一个独立的聚合。评论是聚合根,评论意见是实体。一旦评论被删除,所有的支持/反对意见将毫无意义.

看到这里,我们终于明白了一个真相:相同的领域对象在不同的上下文中,它既可以是实体,也可以是聚合根。回头再看博客园这个例子,博客本身是聚合根没错,它除了和评论关联之外,实际上还关联了合集和标签等其它领域对象。 如果把评论当成博客聚合里的实体的话,你会发现博客这个聚合过于庞大,管理的实体太多,内部逻辑关系也更加复杂,同时还存在不可忽视的性能问题。因此,我们有必要将评论提升成为聚合根,这样既避免了性能问题,同时也让博客聚合根的职责变得简单。这里请思考一个问题:假设博客园改变业务规则,博客可以关闭评论,且评论数量不得超过10条,那么评论可否作为博客聚合中的实体呢?

最后总结

DDD中的聚合是可以拆分的最小功能单元,它是用来封装真正的业务不变性,而不是简单地将对象组合在一起。如果把聚合比作组织,那聚合根就是这个组织的管理者,负责协调实体和值对象,一起完成共同的业务逻辑。同时聚合的设计并不是一成不变的,需要根据业务规则和实际情况来调整,哪怕一开始建模是正确的。还有一点就是聚合要尽量设计得小,这样独立性才高,才能适应业务的变化。以上就是笔者对聚合的一些思考,如有不当之处,请指正.

参考资料

如何运用领域驱动设计 - 聚合 - 句幽 - 博客园 (cnblogs.com) 。

聚合(根)、实体、值对象精炼思考总结 - netfocus - 博客园 (cnblogs.com) 。

最后此篇关于关于DDD中聚合设计的思考(以博客园为例)的文章就讲到这里了,如果你想了解更多关于关于DDD中聚合设计的思考(以博客园为例)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com