- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
聚合作为领域模型中重要的业务功能单元,它的设计是领域建模过程中非常重要的工作。其中聚合根的判断并非一件易事,往往给人一种似是而非的感觉,让人难以捉摸,陷入两难的境地。今天笔者就想以博客园为例来探讨下:博客 (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的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
这里有一个很大的设计缺陷,但我无法解决它: 业务需要有点涉及,所以我会尽量保持简单。 我们有一张购物表和一张退货表。当进行退货时,我们必须找到与数据库中最早购买的退货匹配,并将其记录在“已应用退货”表
在我的家庭项目中,我遇到了确定域对象类型的问题。 领域:公交时刻表 限界上下文:路由(公共(public)交通基础设施,ctx1)、时间表(调度,ctx2) 对象: Station - 描述一个公交车
我有一个名为“产品类型”的值类型,它被分配给了一个产品。 (一个产品有一个产品类型) 为了允许用户从列表中选择类型,我将填充一个下拉列表。在哪里检索产品类型列表最合适?一个实现存储库模式的类? 编辑:
域这个词在 DDD 中究竟是什么意思?我一直在阅读定义……虽然我看到了域模型之类的东西并理解模型是什么 - 域模型是什么意思? 域实际上是什么意思? 谢谢 最佳答案 域是指您的应用程序解决的主题。 例
DDD 中的域模型应该与持久性无关。 CQRS 要求我为我不想在读取模型中包含的所有内容触发事件。 (顺便说一下,将我的模型拆分为一个写模型和至少一个读模型)。 ES 要求我为所有改变状态的事件触发事
Eric 在他的书中触及了 的主题。模块 很少。他也没有通过示例讨论模块结构与有界上下文的关系。限界上下文是否包含模块或模块包含限界上下文?当应用程序具有 DDD 时,它的可扩展性有多容易? 在我们设
前言 笔者于2021年入职了杭州一家做水务系统的公司,按照部门经理要求,新人需要做一次个人分享(主题随意)。 当时笔者对DDD充满了浓厚的兴趣,之前也牛刀小试过,于是就决定班门弄斧Show一下
上部分 模型驱动设计的构造块 为维护模型和实现之间的关系打下了基础。在开发过程中使用一系列成熟的基本构造块并运用一致的语言,能够使开发工作更加清晰而有条理。 我们面临的真正挑战是找到
3. 领域对象的生命周期 。 每个对象都有生命周期,如下图所示。对象自创建后,可能会经历各种不同的状态,直至最终消亡——要么存档,要么删除。当然很多对象是简单的临时对象,仅通过调用构造函数来创建
为了保证软件实践得简洁并且与模型保持一致,不管实际情况如何复杂,必须运用建模和设计的实践. 某些设计决策能够使模型和程序紧密结合在一起,互相促进对方的效用。这种结合要求我们注意每个元素的细节
大家好,我是 ddd 设计的新手,正在尝试使用这种在 C# 中工作的模式开发我的第一个应用程序 在我的应用程序中,我有一个包含子实体 Assets 的聚合合约,当添加或结算 Assets 时,我应该在
我正在尝试弄清楚如何使项目的一些消费者(业务客户)的不变量保持一致,他们对同一版本的聚合根有自己的要求。让我们以客户为例,提出假设性问题以满足以下愚蠢的逻辑: public class Custome
我见过一些具有实体值对象表示的 DDD 项目。它们通常显示为 EmployeeDetail、EmployeeDescriptor、EmployeeRecord 等。有时它包含实体 ID,有时不包含。
我试图了解如何表示某些 DDD(域驱动设计)规则。 遵循蓝皮书约定,我们有: 根实体具有全局身份并负责检查不变量。 根实体控制访问,并且不会被其内部结构的更改所蒙蔽。 对内部成员的 transient
我对 ddd 中的验证方法有疑问。我已经阅读了相当有争议的意见。有人说这应该在实体之外,其他人说这应该放在实体中。我试图找到一种我可以遵循的方法。 例如,假设我有带有电子邮件和密码的 User 实体。
寻找有关如何解决此问题的建议,并了解域驱动设计是否真的是这里的最佳模式。 我的客户正在重新构建其几近过时的工具和服务堆栈。客户是一个快速扩张的电子商户。它的核心产品是它的大型电子商务网站。围绕该网站,
我很难找出实现依赖于数据库中存储的数据的业务规则验证的最佳方法。在下面的简化示例中,我想确保用户名属性是唯一的。 public class User() { public int Id { g
情况: 要处理域事件,Jimmy Bogart proposed 一种将事件存储在聚合中的方法。 在我看来,这是一种非常方便的方法。但是,域服务中的域事件怎么办? 域服务不应该有状态(stateles
我正在处理遗留项目,试图改进项目结构。我的问题是我应该如何组织代码结构。我看到两个选项: #1 business-domain / layer app/ ----accout/ --------app
根据 DDD 原则,所有处理与特定聚合根对象相关的实体的 CRUD 操作都应该由聚合根进行。 但是我们如何从 aggr 根中仅更改实体的单个属性?我们应该在实体中有 setter 方法吗?这些方法应该
我是一名优秀的程序员,十分优秀!