介绍

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

领域划分

首先我们先对比下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 进行获取

    public class Blog:FullAuditedAggregateRoot<Guid>
{
[NotNull]
public virtual string Name { get; set; } [NotNull]
public virtual string ShortName { get; set; } [CanBeNull]
public virtual string Description { get; set; }
} public class Post : FullAuditedAggregateRoot<Guid>
{
public virtual Guid BlogId { get; protected set; } [NotNull]
public virtual string Url { get; protected set; } [NotNull]
public virtual string CoverImage { get; set; } [NotNull]
public virtual string Title { get; protected set; } [CanBeNull]
public virtual string Content { get; set; } [CanBeNull]
public virtual string Description { get; set; } public virtual int ReadCount { get; protected set; } public virtual Collection<PostTag> Tags { get; protected set; }
}

聚合根/实体中的主键

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

    public class Blog:FullAuditedAggregateRoot<Guid>
{
[NotNull]
public virtual string Name { get; set; } [NotNull]
public virtual string ShortName { get; set; } [CanBeNull]
public virtual string Description { get; set; }
}

聚合根/实体构造函数

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

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

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

实体属性访问器和⽅法

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

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

  • 当需要在设置属性时,执⾏任何逻辑,请将属性设置为私有 private 。
  • 定义公共⽅法来操作这些属性。
     public class Blog:FullAuditedAggregateRoot<Guid>
{
[NotNull]
public virtual string Name { get; protected set; } [NotNull]
public virtual string ShortName { get; protected set; } [CanBeNull]
public virtual string Description { get; set; } protected Blog()
{
/*反序列化或ORM 需要*/
} public Blog(Guid id, [NotNull] string name, [NotNull] string shortName)
{
//属性赋值
Id = id;
//有效性检测
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
//有效性检测
ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
} public virtual Blog SetName([NotNull] string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
return this;
} public virtual Blog SetShortName(string shortName)
{
ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
return this;
} }

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

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

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

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

完成聚合的实体创建

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

public class Blog:FullAuditedAggregateRoot<Guid>
{
[NotNull]
public virtual string Name { get; protected set; } [NotNull]
public virtual string ShortName { get; protected set; } [CanBeNull]
public virtual string Description { get; set; } protected Blog()
{ } public Blog(Guid id, [NotNull] string name, [NotNull] string shortName)
{
//属性赋值
Id = id;
//有效性检测
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
//有效性检测
ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
} public virtual Blog SetName([NotNull] string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
return this;
} public virtual Blog SetShortName(string shortName)
{
ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
return this;
} } public class Comment : FullAuditedAggregateRoot<Guid>
{
public virtual Guid PostId { get; protected set; } public virtual Guid? RepliedCommentId { get; protected set; } public virtual string Text { get; protected set; } protected Comment()
{ } public Comment(Guid id, Guid postId, Guid? repliedCommentId, [NotNull] string text)
{
Id = id;
PostId = postId;
RepliedCommentId = repliedCommentId;
Text = Check.NotNullOrWhiteSpace(text, nameof(text));
} public void SetText(string text)
{
Text = Check.NotNullOrWhiteSpace(text, nameof(text));
}
} public class Post : FullAuditedAggregateRoot<Guid>
{
public virtual Guid BlogId { get; protected set; } [NotNull]
public virtual string Url { get; protected set; } [NotNull]
public virtual string CoverImage { get; set; } [NotNull]
public virtual string Title { get; protected set; } [CanBeNull]
public virtual string Content { get; set; } [CanBeNull]
public virtual string Description { get; set; } public virtual int ReadCount { get; protected set; } public virtual Collection<PostTag> Tags { get; protected set; } protected Post()
{ } public Post(Guid id, Guid blogId, [NotNull] string title, [NotNull] string coverImage, [NotNull] string url)
{
Id = id;
BlogId = blogId;
Title = Check.NotNullOrWhiteSpace(title, nameof(title));
Url = Check.NotNullOrWhiteSpace(url, nameof(url));
CoverImage = Check.NotNullOrWhiteSpace(coverImage, nameof(coverImage)); Tags = new Collection<PostTag>();
Comments = new Collection<Comment>();
} public virtual Post IncreaseReadCount()
{
ReadCount++;
return this;
} public virtual Post SetTitle([NotNull] string title)
{
Title = Check.NotNullOrWhiteSpace(title, nameof(title));
return this;
} public virtual Post SetUrl([NotNull] string url)
{
Url = Check.NotNullOrWhiteSpace(url, nameof(url));
return this;
} public virtual void AddTag(Guid tagId)
{
Tags.Add(new PostTag(Id, tagId));
} public virtual void RemoveTag(Guid tagId)
{
Tags.RemoveAll(t => t.TagId == tagId);
} } public record PostTag
{
public virtual Guid TagId { get; init; } //主键 protected PostTag()
{ } public PostTag( Guid tagId)
{
TagId = tagId;
}
} public class Tag : FullAuditedAggregateRoot<Guid>
{
public virtual Guid BlogId { get; protected set; } public virtual string Name { get; protected set; } public virtual string Description { get; protected set; } public virtual int UsageCount { get; protected internal set; } protected Tag()
{ } public Tag(Guid id, Guid blogId, [NotNull] string name, int usageCount = 0, string description = null)
{
Id = id;
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
BlogId = blogId;
Description = description;
UsageCount = usageCount;
} public virtual void SetName(string name)
{
Name = Check.NotNullOrWhiteSpace(name, nameof(name));
} public virtual void IncreaseUsageCount(int number = 1)
{
UsageCount += number;
} public virtual void DecreaseUsageCount(int number = 1)
{
if (UsageCount <= 0)
{
return;
} if (UsageCount - number <= 0)
{
UsageCount = 0;
return;
} UsageCount -= number;
} public virtual void SetDescription(string description)
{
Description = description;
} }

结语

本节知识点:

  • 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. 2012年第三届蓝桥杯C/C++程序设计本科B组省赛 密码发生器

    密码发生器 题目描述: ```bash 在对银行账户等重要权限设置密码的时候,我们常常遇到这样的烦恼:如果为了好记用生日吧,容易被破解,不安全:如果设置不好记的密码,又担心自己也会忘记:如果写在纸上, ...

  2. 利用C语言判定用户输入数据从而给出结果(利用判定用户体重范围)同求最优解!!!

    例子: 要求:医务工作者通过广泛的调查和统计分析,根据成人的身高与体重因素给出了按"体质指数"进行判断的方法,具体如下: 体质指数t=体重 w/(身高h)2(w的单位为kg,h的单 ...

  3. Python获取list中指定元素的索引

    在平时开发过程中,经常遇到需要在数据中获取特定的元素的信息,如到达目的地最近的车站,橱窗里面最贵的物品等等.怎么办?看下面 方法一: 利用数组自身的特性 list.index(target), 其中a ...

  4. Java中为什么notify()可能导致死锁,而notifyAll()则不会(针对生产者-消费者模式)

    1.先说两个概念:锁池 和 等待池 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线 ...

  5. Spring中定时任务@Scheduled的一点小小研究

    最近做一个公众号项目,微信公众号会要求服务端找微信请求一个access_token,获取的过程: access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_tok ...

  6. ES6 模版字符串及常用的es6扩展方法

    1.ES6 模版字符串es6 模版字符串主要用于简化字符串的拼接 <script type="text/javascript"> let obj={name:'rdb' ...

  7. 前端开发入门到进阶第三集【Jsonp】

    /* $.ajax({ type : "get", url : "${loginInfo.SSO_BASE_URL }/user/token/" + token ...

  8. 微信小程序云开发-数据库-获取用户添加的数据到数据库

    一.列表页面新增[添加商品]按钮 在列表页增加[添加商品]按钮,按钮绑定事件toAdd(),用户点击该按钮跳转到添加商品页面. 在js文件中写toAdd()函数,作用是点击[添加商品]按钮,跳转到[添 ...

  9. PAT乙级:1014 福尔摩斯的约会 (20分)

    PAT乙级:1014 福尔摩斯的约会 (20分) 题干 大侦探福尔摩斯接到一张奇怪的字条:我们约会吧! 3485djDkxh4hhGE 2984akDfkkkkggEdsb s&hgsfdk ...

  10. MySql数据库-查询、插入数据时转义函数的使用

    最近在看一部php的基础视频教程,在做案例的时,当通过用户名查询用户信息的时候,先使用了转义函数对客户提交的内容进行过滤之后再交给sql语句进行后续的操作.虽然能看到转义函数本身的作用,但是仍然有一些 ...