|
|
|
@ -32,11 +32,11 @@
|
|
|
|
|
|
|
|
|
|
领域驱动设计(DDD)是一种将实现与**持续进化**的模型连接在一起来满足**复杂**需求的软件开发方法.
|
|
|
|
|
|
|
|
|
|
DDD适用于**复杂领域**或**较大规模**的系统,而不是简单的CRUD程序.它着重与**核心领域逻辑**,而不是基础架构.这样有助于构建一个**灵活**,模块化,**可维护**的代码库.
|
|
|
|
|
DDD适用于**复杂领域**或**较大规模**的系统,而不是简单的CRUD程序.它着重于**核心领域逻辑**,而不是基础架构.这样有助于构建一个**灵活**,模块化,**可维护**的代码库.
|
|
|
|
|
|
|
|
|
|
### OOP & SOLID
|
|
|
|
|
|
|
|
|
|
实现DDD高度依赖面对对象编程思想(OOP)和[SOLID](https://zh.wikipedia.org/wiki/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1))原则.事实上,DDD已经**实现**并**延伸**了这些原则,因此,**深入了解**OOP和SOLID对实施DDD十分有利.
|
|
|
|
|
实现DDD高度依赖面向对象编程思想(OOP)和[SOLID](https://zh.wikipedia.org/wiki/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1))原则.事实上,DDD已经**实现**并**延伸**了这些原则,因此,**深入了解**OOP和SOLID对实施DDD十分有利.
|
|
|
|
|
|
|
|
|
|
### DDD分层与整洁架构
|
|
|
|
|
|
|
|
|
@ -154,7 +154,7 @@ ABP的启动解决方案中包含两个用于集成Entity Framework Core的项
|
|
|
|
|
* `Application` 依赖`Application.Contracts`项目,因为此项目需要实现应用服务的接口及接口使用的DTO.另外也依赖`Domain`项目,因为应用服务的实现必须依赖领域层中的对象.
|
|
|
|
|
* `EntityFrameworkCore` 依赖`Domain`项目,因为此项目需要将领域对象(实体或值对象)映射到数据库的表,另外还需要实现`Domain`项目中的仓储接口.
|
|
|
|
|
* `HttpApi` 依赖`Application.Contracts`项目,因为Controllers需要注入应用服务.
|
|
|
|
|
* `HttpApi.Client` 依赖`Application.Contracts`项目,因为此项目需要是使用应用服务.
|
|
|
|
|
* `HttpApi.Client` 依赖`Application.Contracts`项目,因为此项目需要使用应用服务.
|
|
|
|
|
* `Web` 依赖`HttpApi`项目,因为此项目对外提供HTTP APIs.另外Pages或Components 需要使用应用服务,所以还间接依赖了`Application.Contracts`项目
|
|
|
|
|
|
|
|
|
|
#### 虚线依赖
|
|
|
|
@ -198,7 +198,7 @@ ABP的启动解决方案中包含两个用于集成Entity Framework Core的项
|
|
|
|
|
|
|
|
|
|
##### 关于数据库独立原则的讨论
|
|
|
|
|
|
|
|
|
|
**原因1**会非常影响你**领域对象的建模**(特别是实体间的关系)及**应用程序的代码**.假如,开始选择了关系型数据库,并使用了[Entity Framework Core](Entity-Framework-Core.md),后面尝试切换到[MongoDB](MongoDB.md),那么 **EF Core 中一些非常用的特性**你就不能使用了,例如:
|
|
|
|
|
**原因1**会非常影响你**领域对象的建模**(特别是实体间的关系)及**应用程序的代码**.假如,开始选择了关系型数据库,并使用了[Entity Framework Core](Entity-Framework-Core.md),后面尝试切换到[MongoDB](MongoDB.md),那么 **EF Core 中一些非常有用的特性**你就不能使用了,例如:
|
|
|
|
|
|
|
|
|
|
* 无法使用[变更追踪](https://docs.microsoft.com/zh-cn/ef/core/querying/tracking) ,因为*MongoDB provider*没有提供此功能,因此,你始终需要显式的更新已变更的实体.
|
|
|
|
|
* 无法在不同的聚合间使用[导航属性](https://docs.microsoft.com/zh-cn/ef/core/modeling/relationships),因为文档型数据库是不支持的.有关更多信息,请参见"规则:聚合间仅通过Id关联".
|
|
|
|
@ -209,7 +209,7 @@ ABP的启动解决方案中包含两个用于集成Entity Framework Core的项
|
|
|
|
|
|
|
|
|
|
#### 展现层技术无关原则
|
|
|
|
|
|
|
|
|
|
展现层技术(UI框架)时现代应用程序中最多变的部分之一.**领域层和应用层**应该对展现层所采用的技术或框架**一无所知**.使用ABP启动模板就非常容易实现此原则.
|
|
|
|
|
展现层技术(UI框架)是现代应用程序中最多变的部分之一.**领域层和应用层**应该对展现层所采用的技术或框架**一无所知**.使用ABP启动模板就非常容易实现此原则.
|
|
|
|
|
|
|
|
|
|
在某些情况下,你可能需要在应用层和展现层中写重复的逻辑,例如,参数验证和授权检查.展现层检查出于**用户体验**,应用层或领域层检查出于**数据安全性**和**数据完整性**.
|
|
|
|
|
|
|
|
|
@ -259,7 +259,7 @@ DDD忽略**领域对象的数据展示**,这并不意味着它们并不重要,
|
|
|
|
|
|
|
|
|
|
这样做的原因是我们需要执行业务规则来保证数据的一致性和完整性.假如有一个业务规则:"用户不能对已锁定的问题进行评论".那如何在不查询数据库的情况下,获取问题是否已被锁定?所以,只有关联的对象都被加载了的时候,我们才可以执行业务规则.
|
|
|
|
|
|
|
|
|
|
另外,使用**MongoDB**的开发人员就认为此原则很好理解.在MongoDB中,聚合对象(包含子集合)会被保存到一个`collection`中.因而,无需任何其它配置,就可以实现查询一个聚合,同时所有子对象.
|
|
|
|
|
另外,使用**MongoDB**的开发人员就认为此原则很好理解.在MongoDB中,聚合对象(包含子集合)会被保存到一个`collection`中.因而,无需任何其它配置,就可以实现查询一个聚合,同时包含所有子对象.
|
|
|
|
|
|
|
|
|
|
ABP框架有助于你实现这一原则
|
|
|
|
|
|
|
|
|
@ -293,7 +293,7 @@ public class IssueAppService : ApplicationService, IIssueAppService
|
|
|
|
|
|
|
|
|
|
最后,我们使用`_issueRepository.UpdateAsync`方法,将对象保存到数据库中.
|
|
|
|
|
|
|
|
|
|
> EF Core 具有**变更追踪**的功能,因此,不需要调用`_issueRepository.UpdateAsync`方法.ABP的工作单元会再方法结束时,自动执行`DbContext.SaveChanges()`的.如果使用MongoDB则需要显式手动调用.
|
|
|
|
|
> EF Core 具有**变更追踪**的功能,因此,不需要调用`_issueRepository.UpdateAsync`方法.ABP的工作单元会在方法结束时,自动执行`DbContext.SaveChanges()`的.如果使用MongoDB则需要显式手动调用.
|
|
|
|
|
>
|
|
|
|
|
> 因此,当需要额外编写仓储层的实现,应该在实体变化时始终调用 `UpdateAsync` 方法.
|
|
|
|
|
|
|
|
|
@ -375,7 +375,7 @@ MongoDB中不适合使用导航属性或集合的,原因是:当前源聚合对
|
|
|
|
|
|
|
|
|
|
并不是所有的子集合的主键都是联合主键,有些情况下,可以使用单独的`Id`作为主键.
|
|
|
|
|
|
|
|
|
|
> 联合主键实际上时关系型数据库中的概念,因为子集合对象有与之对应的数据库表,而表也要有主键.但是在非关系型数据库中,无需为子集合实体定义主键,因为它们本身就已属于一个聚合根.
|
|
|
|
|
> 联合主键实际上是关系型数据库中的概念,因为子集合对象有与之对应的数据库表,而表也要有主键.但是在非关系型数据库中,无需为子集合实体定义主键,因为它们本身就已属于一个聚合根.
|
|
|
|
|
|
|
|
|
|
##### 聚合根 / 实体的构造函数
|
|
|
|
|
|
|
|
|
@ -1017,7 +1017,7 @@ public class IssueAppService : ApplicationService, IIssueAppService
|
|
|
|
|
|
|
|
|
|
### 领域服务
|
|
|
|
|
|
|
|
|
|
领域服务主要来实现本领域的逻辑:
|
|
|
|
|
领域服务主要用来实现本领域的逻辑:
|
|
|
|
|
|
|
|
|
|
* 依赖**服务和仓储**.
|
|
|
|
|
* 需要使用多个聚合.
|
|
|
|
@ -1276,7 +1276,7 @@ public class UserChangePasswordDto
|
|
|
|
|
|
|
|
|
|
虽然编写了更多的代码,但是这样可维护性更高.
|
|
|
|
|
|
|
|
|
|
**例外情况:**该规则有一些例外的情况,例如,你想开发两个方法,它们共用相同的输入DTO(通过继承或重用),有一个报表页面有多个过滤条件,多个应用服务使用相同的输入参数返回不同的结果(如,大屏展示数据,Excel报表,csv报表).这种情况下,你是需要修改一个参数,多个应用服务都应该一起被修改.
|
|
|
|
|
**例外情况:** 该规则有一些例外的情况,例如,你想开发两个方法,它们共用相同的输入DTO(通过继承或重用),有一个报表页面有多个过滤条件,多个应用服务使用相同的输入参数返回不同的结果(如,大屏展示数据,Excel报表,csv报表).这种情况下,你是需要修改一个参数,多个应用服务都应该一起被修改.
|
|
|
|
|
|
|
|
|
|
##### 输入DTO中验证逻辑
|
|
|
|
|
|
|
|
|
@ -1635,7 +1635,7 @@ public class IssueCreationDto
|
|
|
|
|
|
|
|
|
|
因为,应用服务可能在保存`Issue`对象之前,需要对其它对象进行修改.如果领域服务执行了保存,那么*保存*操作就是重复的.
|
|
|
|
|
|
|
|
|
|
* 会触发两次数据库会交互,这会导致性能损失.
|
|
|
|
|
* 会触发两次数据库交互,这会导致性能损失.
|
|
|
|
|
* 需要额外添加显式的事务来包含这两个操作,才能保证数据一致性.
|
|
|
|
|
* 如果因为业务规则取消了实体的创建,则应该在数据库事务中回滚事务,取消所有操作.
|
|
|
|
|
|
|
|
|
@ -1966,7 +1966,7 @@ public class IssueAppService
|
|
|
|
|
* 如果没有**任何业务逻辑**,只有简单的**CRUD**操作,**请勿**创建领域服务.
|
|
|
|
|
* **切勿**将**DTO**传递给领域服务,或从领域服务返回**DTO**.
|
|
|
|
|
|
|
|
|
|
可以在应用服务中直接注入仓储,实现查询,创建,更新及删除操作.除非在这些操作过程中需要执行某些业务逻辑,在这种情况下,请创建领域服务.
|
|
|
|
|
可以在应用服务中直接注入仓储,实现查询,创建,更新及删除操作.除非在这些操作过程中需要执行某些领域逻辑,在这种情况下,请创建领域服务.
|
|
|
|
|
|
|
|
|
|
> 不要创建"将来可能需要"这种CRUD领域服务方法([YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it)),在需要时重构它并重构现有代码. 由于应用层优雅地抽象了领域层,因此重构过程不会影响UI层和其他客户端.
|
|
|
|
|
|
|
|
|
|