介绍

我们将通过例⼦介绍和解释⼀些显式规则。在实现领域驱动设计时,应该遵循这些规则并将其应⽤到解决⽅案中。

领域划分

首先我们先对比下Blog.Core和本次重构设计上的偏差,可以看到多了一个博客管理和类别管理。

根据上面得到的业务脑图我们可以看到包含Blog(博客),Post(文章),Comment(评论),Tag(标签),User(用户),根据脑图画出领域图来指明关系。

领域图连接地址:https://www.processon.com/view/link/611365c00e3e7407d39727ee

聚合根最佳实践

只通过ID引⽤其他聚合

⼀个聚合应该只通过其他聚合的ID引⽤聚合,这意味着你不能添加导航属性到其他聚合。

  • 这条规则使得实现可序列化原则得以实现。

  • 可以防⽌不同聚合相互操作,以及将聚合的业务逻辑泄露给另⼀个聚合。

来看下面的2个聚合根 Blog 和 Post.

  • Blog 没有包含 Post集合,因为他们是不同聚合
  • Post 使用 BlogId 关联 Blog

当你有一个 Post 需要关联 Blog的时候 你可以从数据库通过 BlogId 进行获取

  1. public class Blog:FullAuditedAggregateRoot<Guid>
  2. {
  3. [NotNull]
  4. public virtual string Name { get; set; }
  5. [NotNull]
  6. public virtual string ShortName { get; set; }
  7. [CanBeNull]
  8. public virtual string Description { get; set; }
  9. }
  10. public class Post : FullAuditedAggregateRoot<Guid>
  11. {
  12. public virtual Guid BlogId { get; protected set; }
  13. [NotNull]
  14. public virtual string Url { get; protected set; }
  15. [NotNull]
  16. public virtual string CoverImage { get; set; }
  17. [NotNull]
  18. public virtual string Title { get; protected set; }
  19. [CanBeNull]
  20. public virtual string Content { get; set; }
  21. [CanBeNull]
  22. public virtual string Description { get; set; }
  23. public virtual int ReadCount { get; protected set; }
  24. public virtual Collection<PostTag> Tags { get; protected set; }
  25. }

聚合根/实体中的主键

⼀个聚合根通常有⼀个ID属性作为其标识符(主键,Primark Key: PK)。推荐使⽤ Guid 作为聚合,聚合中的实体(不是聚合根)可以使⽤复合主键(后面讲),主键ABP已经帮我们做好了参阅文档:https://docs.abp.io/en/abp/latest/Entities。

  1. public class Blog:FullAuditedAggregateRoot<Guid>
  2. {
  3. [NotNull]
  4. public virtual string Name { get; set; }
  5. [NotNull]
  6. public virtual string ShortName { get; set; }
  7. [CanBeNull]
  8. public virtual string Description { get; set; }
  9. }

聚合根/实体构造函数

构造函数是实体的⽣命周期开始的地⽅。⼀个设计良好的构造函数,担负以下职责:

  • 获取所需的实体属性参数,来创建⼀个有效的实体。应该强制只传递必要的参数,并可以将⾮必要 的属性作为可选参数。
  • 检查参数的有效性。
  • 初始化⼦集合。

  1. public Blog(Guid id, [NotNull] string name, [NotNull] string shortName)
  2. {
  3. //属性赋值
  4. Id = id;
  5. //有效性检测
  6. Name = Check.NotNullOrWhiteSpace(name, nameof(name));
  7. //有效性检测
  8. ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
  9. }
  • Blog 类通过构造函数参数、获得属性所需的值,以此创建一个正确有效的实体
  • 在构造函数中验证输⼊参数的有效性,⽐如: Check.NotNullOrWhiteSpace(...) 当传递的值为空 时,抛出异常 ArgumentException
  • 构造函数将参数 id 传递给 base 类,不在构造函数中⽣成 Guid,可以将其委托给另⼀个 Guid⽣成 服务,作为参数传递进来
  • ⽆参构造函数对于ORM是必要的。我们将其设置为私有,以防⽌在代码中意外地使⽤它

实体属性访问器和⽅法

上⾯的示例代码,看起来可能很奇怪。⽐如:在构造函数中,我们强制传递⼀个不为 null 的 Name 。 但是,我们可以将 Name 属性设置为 null ,⽽对其没有进⾏任何有效性控制。

如果我们⽤ public 设置器声明所有的属性,就像上⾯的 Blog 类中的属性例⼦,我们就不能在实体的⽣命周期中强制保持其有效性和完整性。所以:

  • 当需要在设置属性时,执⾏任何逻辑,请将属性设置为私有 private 。
  • 定义公共⽅法来操作这些属性。
  1. public class Blog:FullAuditedAggregateRoot<Guid>
  2. {
  3. [NotNull]
  4. public virtual string Name { get; protected set; }
  5. [NotNull]
  6. public virtual string ShortName { get; protected set; }
  7. [CanBeNull]
  8. public virtual string Description { get; set; }
  9. protected Blog()
  10. {
  11. /*反序列化或ORM 需要*/
  12. }
  13. public Blog(Guid id, [NotNull] string name, [NotNull] string shortName)
  14. {
  15. //属性赋值
  16. Id = id;
  17. //有效性检测
  18. Name = Check.NotNullOrWhiteSpace(name, nameof(name));
  19. //有效性检测
  20. ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
  21. }
  22. public virtual Blog SetName([NotNull] string name)
  23. {
  24. Name = Check.NotNullOrWhiteSpace(name, nameof(name));
  25. return this;
  26. }
  27. public virtual Blog SetShortName(string shortName)
  28. {
  29. ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
  30. return this;
  31. }
  32. }

业务逻辑和实体中的异常处理

当你在实体中进⾏验证和实现业务逻辑,经常需要管理异常:

  • 创建特定领域异常。
  • 必要时在实体⽅法中抛出这些异常

ABP框架 Exception Handing 系统处理了这些问题。

完成聚合的实体创建

根据 最佳实践的讲解完成,把其他实体创建出来,代码粘在这里了。

  1. public class Blog:FullAuditedAggregateRoot<Guid>
  2. {
  3. [NotNull]
  4. public virtual string Name { get; protected set; }
  5. [NotNull]
  6. public virtual string ShortName { get; protected set; }
  7. [CanBeNull]
  8. public virtual string Description { get; set; }
  9. protected Blog()
  10. {
  11. }
  12. public Blog(Guid id, [NotNull] string name, [NotNull] string shortName)
  13. {
  14. //属性赋值
  15. Id = id;
  16. //有效性检测
  17. Name = Check.NotNullOrWhiteSpace(name, nameof(name));
  18. //有效性检测
  19. ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
  20. }
  21. public virtual Blog SetName([NotNull] string name)
  22. {
  23. Name = Check.NotNullOrWhiteSpace(name, nameof(name));
  24. return this;
  25. }
  26. public virtual Blog SetShortName(string shortName)
  27. {
  28. ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
  29. return this;
  30. }
  31. }
  32. public class Comment : FullAuditedAggregateRoot<Guid>
  33. {
  34. public virtual Guid PostId { get; protected set; }
  35. public virtual Guid? RepliedCommentId { get; protected set; }
  36. public virtual string Text { get; protected set; }
  37. protected Comment()
  38. {
  39. }
  40. public Comment(Guid id, Guid postId, Guid? repliedCommentId, [NotNull] string text)
  41. {
  42. Id = id;
  43. PostId = postId;
  44. RepliedCommentId = repliedCommentId;
  45. Text = Check.NotNullOrWhiteSpace(text, nameof(text));
  46. }
  47. public void SetText(string text)
  48. {
  49. Text = Check.NotNullOrWhiteSpace(text, nameof(text));
  50. }
  51. }
  52. public class Post : FullAuditedAggregateRoot<Guid>
  53. {
  54. public virtual Guid BlogId { get; protected set; }
  55. [NotNull]
  56. public virtual string Url { get; protected set; }
  57. [NotNull]
  58. public virtual string CoverImage { get; set; }
  59. [NotNull]
  60. public virtual string Title { get; protected set; }
  61. [CanBeNull]
  62. public virtual string Content { get; set; }
  63. [CanBeNull]
  64. public virtual string Description { get; set; }
  65. public virtual int ReadCount { get; protected set; }
  66. public virtual Collection<PostTag> Tags { get; protected set; }
  67. protected Post()
  68. {
  69. }
  70. public Post(Guid id, Guid blogId, [NotNull] string title, [NotNull] string coverImage, [NotNull] string url)
  71. {
  72. Id = id;
  73. BlogId = blogId;
  74. Title = Check.NotNullOrWhiteSpace(title, nameof(title));
  75. Url = Check.NotNullOrWhiteSpace(url, nameof(url));
  76. CoverImage = Check.NotNullOrWhiteSpace(coverImage, nameof(coverImage));
  77. Tags = new Collection<PostTag>();
  78. Comments = new Collection<Comment>();
  79. }
  80. public virtual Post IncreaseReadCount()
  81. {
  82. ReadCount++;
  83. return this;
  84. }
  85. public virtual Post SetTitle([NotNull] string title)
  86. {
  87. Title = Check.NotNullOrWhiteSpace(title, nameof(title));
  88. return this;
  89. }
  90. public virtual Post SetUrl([NotNull] string url)
  91. {
  92. Url = Check.NotNullOrWhiteSpace(url, nameof(url));
  93. return this;
  94. }
  95. public virtual void AddTag(Guid tagId)
  96. {
  97. Tags.Add(new PostTag(Id, tagId));
  98. }
  99. public virtual void RemoveTag(Guid tagId)
  100. {
  101. Tags.RemoveAll(t => t.TagId == tagId);
  102. }
  103. }
  104. public record PostTag
  105. {
  106. public virtual Guid TagId { get; init; } //主键
  107. protected PostTag()
  108. {
  109. }
  110. public PostTag( Guid tagId)
  111. {
  112. TagId = tagId;
  113. }
  114. }
  115. public class Tag : FullAuditedAggregateRoot<Guid>
  116. {
  117. public virtual Guid BlogId { get; protected set; }
  118. public virtual string Name { get; protected set; }
  119. public virtual string Description { get; protected set; }
  120. public virtual int UsageCount { get; protected internal set; }
  121. protected Tag()
  122. {
  123. }
  124. public Tag(Guid id, Guid blogId, [NotNull] string name, int usageCount = 0, string description = null)
  125. {
  126. Id = id;
  127. Name = Check.NotNullOrWhiteSpace(name, nameof(name));
  128. BlogId = blogId;
  129. Description = description;
  130. UsageCount = usageCount;
  131. }
  132. public virtual void SetName(string name)
  133. {
  134. Name = Check.NotNullOrWhiteSpace(name, nameof(name));
  135. }
  136. public virtual void IncreaseUsageCount(int number = 1)
  137. {
  138. UsageCount += number;
  139. }
  140. public virtual void DecreaseUsageCount(int number = 1)
  141. {
  142. if (UsageCount <= 0)
  143. {
  144. return;
  145. }
  146. if (UsageCount - number <= 0)
  147. {
  148. UsageCount = 0;
  149. return;
  150. }
  151. UsageCount -= number;
  152. }
  153. public virtual void SetDescription(string description)
  154. {
  155. Description = description;
  156. }
  157. }

结语

本节知识点:

  • 1.根据脑图划分聚合
  • 2.根据领域图在遵守DDD的聚合根规范的情况下创建聚合

联系作者:加群:867095512 @MrChuJiu

Abp vNext 基础篇丨领域构建的更多相关文章

  1. 六、Abp vNext 基础篇丨文章聚合功能上

    介绍 9月开篇讲,前面几章群里已经有几个小伙伴跟着做了一遍了,遇到的问题和疑惑也都在群里反馈和解决好了,9月咱们保持保持更新.争取10月份更新完基础篇. 另外番外篇属于 我在abp群里和日常开发的问题 ...

  2. 十一、Abp vNext 基础篇丨测试

    前言 祝大家国庆快乐,本来想国庆之前更新完的,结果没写完,今天把剩下的代码补了一下总算ok了. 本章节也是我们后端日常开发中最重要的一步就是测试,我们经常听到的单元测试.集成测试.UI测试.系统测试, ...

  3. 七、Abp vNext 基础篇丨文章聚合功能下

    介绍 不好意思这篇文章应该早点更新的,这几天在忙CICD的东西没顾得上,等后面整好了CICD我也发2篇文章讲讲,咱们进入正题,这一章来补全剩下的 2个接口和将文章聚合进行完善. 开工 上一章大部分业务 ...

  4. Abp vNext 基础篇丨分层架构

    介绍 本章节对 ABP 框架进行一个简单的介绍,摘自ABP官方,后面会在使用过程中对各个知识点进行细致的讲解. 领域驱动设计 领域驱动设计(简称:DDD)是一种针对复杂需求的软件开发方法.将软件实现与 ...

  5. 五、Abp vNext 基础篇丨博客聚合功能

    介绍 业务篇章先从客户端开始写,另外补充一下我给项目起名的时候没多想起的太随意了,结果后面有些地方命名冲突了需要通过手动using不过问题不大. 开工 应用层 根据第三章分层架构里面讲到的现在我们模型 ...

  6. 八、Abp vNext 基础篇丨标签聚合功能

    介绍 本章节先来把上一章漏掉的上传文件处理下,然后实现Tag功能. 上传文件 上传文件其实不含在任何一个聚合中,它属于一个独立的辅助性功能,先把抽象接口定义一下,在Bcvp.Blog.Core.App ...

  7. 十、Abp vNext 基础篇丨权限

    介绍 本章节来把接口的权限加一下 权限配置和使用 官方地址:https://docs.abp.io/en/abp/latest/Authorization 下面这种代码可能我们日常开发都写过,ASP. ...

  8. 九、Abp vNext 基础篇丨评论聚合功能

    介绍 评论本来是要放到标签里面去讲的,但是因为上一章东西有点多了,我就没放进去,这一章单独拿出来,内容不多大家自己写写就可以,也算是对前面讲解的一个小练习吧. 相关注释我也加在代码上面了,大家看看代码 ...

  9. Abp vNext 番外篇-疑难杂症丨浅谈扩展属性与多用户设计

    说明 Abp vNext基础篇的文章还差一个单元测试模块就基本上完成了我争取10.1放假之前给大家赶稿出来,后面我们会开始进阶篇,开始拆一些东西,具体要做的事我会单独开一个文章来讲 缘起 本篇文章缘起 ...

随机推荐

  1. Spring Boot中的那些生命周期和其中的可扩展点(转)

    前言可扩展点的种类Spring Boot启动过程 1.SpringApplication的启动过程 2.ApplicationContext的启动过程 3.一般的非懒加载单例Bean在Spring B ...

  2. docker安装应用整理

    nginx安装: docker run \ --name nginx \ --volume /var/data/nginx/nginx.conf:/etc/nginx/nginx.conf \ --v ...

  3. 灵魂画手的零基础python教程1:关于Python学习的误区、python的优缺点、前景

    滴~ 近段时间,因为工作项目的原因,阿菌要重拾起python这门语言了,所以顺势写一门python教程,精心的编排,配上漫画和视频,希望能帮助更多想接触编程的同学入门,课程将从基础语法开始讲起,和大家 ...

  4. 【深度学习】在linux和windows下anaconda+pycharm+tensorflow+cuda的配置

    在linux和windows下anaconda+pycharm+tensorflow+cuda的配置 在linux和windows下anaconda+pycharm+tensorflow+cuda的配 ...

  5. StreamAPI

    一.简介 Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作,或者大批量数据操作 . Stream API 借助于同样 ...

  6. EasyUI学后总结第一集

    1,创建Easyui组件-使用html还是使用js方式? 如果在创建Easyui组件的时候,组件再被更改,那么属于静态组件,对于静态组件不要使用js方式创建--会增加js代码量. 如果创建的Easyu ...

  7. python程序开机自启动

    windows下设置 因为服务器是windows环境 担心黑窗口不小心被关闭  因此想要让python程序在后台运行 只需要一下几步 1. 在启动python启动文件加入以下代码 import win ...

  8. 小鹤双拼win10一键恢复布局

    起因 一直用的小鹤双拼布局,最近重装系统又要重新配置,麻烦 尝试 查找对应注册表设置,找到以下路径包含相应配置 HKEY_CURRENT_USER\Software\Microsoft\InputMe ...

  9. Floyd弗洛伊德算法

    先看懂如何使用 用Java实现一个地铁票价计算程序 String station = "A1 A2 A3 A4 A5 A6 A7 A8 A9 T1 A10 A11 A12 A13 T2 A1 ...

  10. 🏆(不要错过!)【CI/CD技术专题】「Jenkins实战系列」(3)Jenkinsfile+DockerFile实现自动部署

    每日一句 没有人会因学问而成为智者.学问或许能由勤奋得来,而机智与智慧却有懒于天赋. 前提概要 Jenkins下用DockerFile自动部署Java项目,项目的部署放心推向容器化时代机制. 本节需要 ...