《ASP.NET Core技术内幕与项目实战》精简集-DDD准备5.1:DDD基本概念

本节内容,部分为补充内容,部分涉及到9.1-9.2(P299-311,326-327)。主要NuGet包:无

 

一、DDD领域驱动设计

1、DDD是一种设计思想,领域即业务,业务驱动设计,直接将业务映射到代码中。

2、DDD的设计始于领域的划分,一个项目可以划分为多个子域,并可以按功能划分为核心域、非核心域、支撑域。

3、确定领域后,就要对领域内的对象进行建模,抽象出领域模型,并用领域模型驱动项目的开发。

4、DDD的诞生早于微服务,但DDD是指导微服务架构设计的强有力思想。

 

二、DDD分层实践

1、领域层:实现领域的核心业务逻辑。

2、应用层:基于领域的应用程序用例,可能存在多个应用程序对应于一个领域层。

3、展示层:基于应用程序的UI,WebAPI也是UI层的一种。

4、基础层:提供EFCore等基础设施的支持。

 

三、DDD的基本构件:主要位于领域层和应用层

1、领域层构件

  • 实体(Entity):拥有唯一标识符的对象,实体的其它属性会经历各种变化,但标识符是不变的。具体实践中,实体一般表现为EFcore中的实体类,实体类的Id属性就是标识符,只要Id不变,就是指同一个对象。
  • 值对象(ValueObject):没有标识符的对象,有多个属性,全部属性相同时才视为同一个对象。依附于实体存在,主要用于表示一个整体性的属性组合,如一个经纬度位置。
  • 聚合和聚合根(Aggregate&AggregateRoot):一个系统中会有多个实体(包括值对象),可以将关系紧密的实体类放到一个聚合中,每个聚合中有一个实体作为聚合根,所有对聚合内实体的访问都通过聚合根进行,外部系统只能持有对聚合根的引用。聚合根即是实体类,也是所有聚合内实体的管理者。聚合体现了整体和部分的关系,且有相同的生命周期。如订单和订单明细,删除了订单,订单明细也就消失,而外部系统不能直接引用订单明细,只能引用订单,对订单明细的操作都通过订单来进行。
  • 仓储(接口)(Repository):将数据访问层抽象出来,隐藏了底层对数据源的CRUD操作,被领域层和应用层用来访问数据库。仓储接口位于领域层,仓储实现位于基础设施层。
  • 领域服务(DomainService):聚合根中可以实现聚合内的业务逻辑(充血模型)。但是,当聚合内的业务逻辑需要注入外部服务或仓储时,或者需要实现跨聚合业务逻辑时,应该将业务逻辑抽出,通过领域服务来实现。
  • 领域事件(DomainEvent):一种低耦合的事件订阅和通知方式,实现了开闭原则。聚合类一般不需要事件,同一个微服务中聚合之间的事件传递,通过领域事件实现。如果需要跨微服务实现事件通知机制,则通过集成事件来实现。

 

//命令式开发和事件机制的对比

//使用事务脚本时的伪代码(命令式开发)
void 保存答案(long id, string answer)
{
    long aId = 保存到数据库(id,answer);
    if(检查是否疑似违规(answer))
    {
        隐藏答案(aId);
        通知管理员审核();
    } 
    else
    {
        string email = 获取提问者邮箱(id);
        发送邮件(email,"你的问题被回答了");
    } 
}


//使用事件机制的伪代码
//扩展容易(开闭原则);用户体验端更好;容错性好;
void 保存答案(long id, string answer)
{
    long aId = 保存到数据库(id,answer);
    发布事件("答案已保存",aId,answer);
}

[绑定事件“答案已保存”]
void 审核答案(long aId,string answer)
{
    if(检查是否疑似违规(answer))
    {
        隐藏答案(aId);
        发布事件("内容待审核",aId);
    }
}

[绑定事件“答案已保存”]
void 发邮件给提问者(long aId,string answer)
{
    long qId = 获取问题Id(aId);
    string email = 获取提问者邮箱(qId);
    发送邮件(email,"你的问题被回答了");
}

  

2、应用层构件:

  • 应用服务(ApplicationService):应用程序用例的业务逻辑,通常与外部系统交互,获取和返回数据传输对象。业务逻辑是放在领域服务,还是应用服务中,并不好确认,如果多个应用程序用例,重复使用同一个业务逻辑,应该考虑将这个业务逻辑移入领域服务。
  • 数据传输对象(DTO):DTO是贫血对象,不包含任何业务逻辑,只用于应用层和展示层的数据传递。
  • 工作单元(UOW):聚合内数据操作的关系是非常紧密的,要保证事务的强一致性。聚合间的协作关系不紧密,保证事务的最终一致性即可。对数据库的若干相关联操作,应组成一个工作单元,在工作单元中的操作统一提交,要么全部成功,要么失败全部回滚。

 

 

四、聚合在EFCore中的实践

1、上下文(DbContext),是一个天然的仓储和工作单元,上下文跟踪多个对象状态的改变,然后SaveChanges方法后,一次性提交到数据库,“要么全部成功,要么全部失败”。具体实践中,可以抽像出一个仓储Repository和工作单元接口UOW,以封装上下文操作,也可以直接使用上下文进行操作,这反而是微软推荐的方式。

2、上下文中,只需要对聚合根定义DbSet属性,聚合中其它实体不应该被直接访问到。对于聚合根实体,可以定义一个IAggregateRoot接口,以区分聚合根实体和其它实体类。

3、在一个微服务中,将实体类放到同一个上下文中,跨聚合操作时,可以获取更好的性能,也更容易实现强一致性的事务。
4、聚合之间是松耦合关系,只能通过聚合根的Id进行关联,不能设置导航关系。跨聚合的数据查询,应该通过领域服务来实现,而不能直接通过数据表之间的join来实现(统计汇总除外)。

 

特别说明:
1、本系列内容主要基于杨中科老师的书籍《ASP.NET Core技术内幕与项目实战》及配套的B站视频视频教程,同时会增加极少部分的小知识点
2、本系列教程主要目的是提炼知识点,追求快准狠,以求快速复习,如果说书籍学习的效率是视频的2倍,那么“简读系列”应该做到再快3-5倍

 

posted @ 2022-11-25 00:42  functionMC  阅读(1076)  评论(0编辑  收藏  举报