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

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

业务脑图

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

领域图

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

⼀个聚合应该只通过其他聚合的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

公众号

版权声明:本文为MrChuJiu原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/MrChuJiu/p/15174460.html