首页 > 其他分享 >关于DDD中聚合设计的思考(以博客园为例)

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

时间:2023-08-29 15:24:42浏览次数:45  
标签:聚合 为例 博客园 博客 评论 Guid Id DDD

前言

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

标签:聚合,为例,博客园,博客,评论,Guid,Id,DDD
From: https://www.cnblogs.com/fengjq/p/17659878.html

相关文章

  • View与Widget,以QListView与QListWidget为例
    目录View与Widget的区别和联系如何正确使用QListView与QListWidget使用QListView:使用QListWidget:代码演示总结在Qt框架中,"View"与"Widget"是两个关键概念,它们在用户界面设计和数据展示中发挥重要作用。本篇博客将介绍"View"和"Widget"的区别与联系,然后使用Qt中的QListView和QList......
  • 博客园美化的一些心得和走过的坑
    申请权限申请js权限是一切的开始,不用多说了吧。套用模板对于前端小白来说,最省时间的美化方法就是套用BNDong大神的模板github链接:https://github.com/BNDong/Cnblogs-Theme-SimpleMemory教程官网:https://bndong.github.io/Cnblogs-Theme-SimpleMemory/v2.1/dist/只要跟着快......
  • 产品代码都给你看了,可别再说不会DDD(四):代码工程结构
    这是一个讲解DDD落地的文章系列,作者是《实现领域驱动设计》的译者滕云。本文章系列以一个真实的并已成功上线的软件项目——码如云(https://www.mryqr.com)为例,系统性地讲解DDD在落地实施过程中的各种典型实践,以及在面临实际业务场景时的诸多取舍。本系列包含以下文章:DDD入门DD......
  • 【python】使用ddddocr模块报错处理:AttributeError: module 'PIL.Image' has no attri
    安装pipinstallddddocr安装特别慢,几kb每秒,而且容易超时报错使用清华源下载:pipinstall-ihttps://pypi.tuna.tsinghua.edu.cn/simpleddddocr使用img_url="https://user.wangxiao.cn/apis//common/getImageCaptcha"img_resp=session.post(img_url)......
  • 产品代码都给你看了,可别再说不会DDD(三):战略设计
    这是一个讲解DDD落地的文章系列,作者是《实现领域驱动设计》的译者滕云。本文章系列以一个真实的并已成功上线的软件项目——码如云(https://www.mryqr.com)为例,系统性地讲解DDD在落地实施过程中的各种典型实践,以及在面临实际业务场景时的诸多取舍。本系列包含以下文章:DDD入门DD......
  • CSDN转到博客园,主题美化,经验分享
    1引言之前我也因为百度全是csdn,选择在csdn写博客。写博客的原因就是为了学习,输出倒逼输入。最近正好学习了django,自己从头到尾写了一个django-blog网站,感觉很爽。然后开始想着建站,想有一个好看的博客。一直觉得csdn不好看,没有逼格,没有足够的定制化,而且csdn感觉很乱,很多文章乱......
  • 在博客园中美化博客,并使用自定义的主题
    一、开通博客园JS权限这部分不再赘述,可以点击下方链接,查看如何申请博客园JS权限申请二、在博客园中设置相关内容打开你的博客首页->管理->设置设置博客皮肤为“Custom”勾选禁用默认CSS样式三、在博客园设置中粘贴相应代码此部分代码粘贴到【页面定制......
  • 领域驱动设计(DDD):三层架构到DDD架构演化
    三层架构的问题在前文中,我从基础代码的角度探讨了如何运用领域驱动设计(DDD)来实现高内聚低耦合的代码。本篇文章将从项目架构的角度,继续探讨三层架构与DDD之间的演化过程,以及DDD如何优化架构的问题。三层架构作为一种常见的软件架构模式,将应用程序分为展示层、业务逻辑层和数据访......
  • 博客园Markdown随笔快速备份工具
    1、背景本人花了一个月时间,正在搭建一个能够实时同步博客园文章的博客后台。无奈突然得到了博客园最后破釜沉舟的消息。故花了一天时间快速搭建了这个博客园Markdown文章快速备份工具目前工具已成功运行,你只需要在博客园中获取你的metaweblog的url,name,token即可使用本工具......
  • cmake入门教程——以LLVM、Pytorch为例
    时代变了,已经基本无人写makefile,现在都是使用cmake进行项目构建的。cmake相对来说还是比较简单的,鄙人熟练修改LLVM/Pytorch,我们可以剖析下我比较熟悉项目的cmake配置。一、cmake介绍二、LLVMcmake配置三、Pytorchcmake配置四、总结......