DDD 基础
大约 9 分钟
DDD 基础
微服务概念
单体结构项目缺点:耦合;技术栈统一,软件包版本锁定;一崩全崩;升级周期长;无法局部扩容;
微服务一般是指将高度相关功能的一个开发部署单元,有自己的技术自治性、技术选型、弹性扩缩容、发布上下频率
微服务结构优点:耦合性低,易于开发和维护;可以用不同技术栈;可以单独扩容;互相隔离,影响小;部署周期短;
微服务结构缺点:对运维能力要求高;运行效率会降低;技术要求高,需要处理事务最终一致性等问题。
Domain-driven design,领域驱动设计,是一个很好的应用于微服务架构的方法论
架构演变
IAAS:基础设施服务,Infrastructure-as-a-service
PAAS:平台服务,Platform-as-a-service
SAAS:软件服务,Software-as-a-service
限界上下文 bounded context
限界上下文划分规则
BC 与微服务的关系:BC 其实就是一个领域或一个模块或一个业务,如果两个领域相关性很高,就可以包含多个BC,或者如果一个领域访问量非常大,则需要部署在一个微服务中以提高性能
BC与业务的关系:同一个领域概念在不同的 BC 中可能表现不同的含义
BC与技术的关系:多个子域之间必须需要在应用层进行聚合,而聚合的过程中就引出了技术方案
四重边界
- 子域(领域划分) --> 界限上下文--> 分层设计 --> 聚合设计
- DDD 通过规划四重边界,把领域知识做了合理的固化和分层
- 领域划分:
- 核心域:解决项目的核心问题,和组织业务紧密相关。
- 支撑域:解决项目的非核心问题,则具有组织特性,但不具有通用性。
- 通用域:解决通用问题,没有组织特性。
- 通用语言与界限上下文:
- 通用语言:一个拥有确切含义的、没有二义性的语言
- 通用语言离不开特定的语义环境,只有确定了通用语言所在的边界,才能没有歧义的描述一个业务对象。
基本概念
实体和值对象 Entity/Value Object
- 实体 = 唯一身份标识 + 可变性【状态 + 行为】
- 在数据库中我们一般用表的主键来实现“标识符”
- 实体一般的表现形式就是 EF Core 中的实体类
- 实体不是 ORM 类,应该是具有行为和状态
- 值对象 = 将一个值用对象的方式进行表述,来表达一个具体的固定不变的概念
- 值对象没有标志符,可以具有多个属性,作为实体的属性依附于实体而存在
- 实体通过标识符区分是否是同一个实体,而值对象通过其属性进行区分,即值对象所有属性相同则可认为是同一个值对象
- 值对象的设计应尽量简单,不要让它引用很多其他的对象
聚合及聚合根 aggregate/aggregate root
- 目的:高内聚,低耦合。
- 聚合是用来定义领域对象所有权和边界的领域模式
- 把关系紧密的实体放到一个聚合中,每个聚合中有一个实体作为聚合根(Aggregate Root),所有对于聚合内对象的访问都通过聚合根来进行,外部对象只能持有对聚合根的引用。
- 聚合根不仅仅是实体,还是所在聚合的管理者
- 聚合的划分的原则:
- 尽量把聚合设计的小一点,一个聚合只包含一个聚合根实体和密不可分的实体,实体中只包含最小数量的属性
- 小聚合有助于进行微服务的拆分
- 通常把聚合组织到一个文件夹或一个包中,并且每个聚集成员包括实体、值对象,domain事件,仓储接口和其它工厂对象。
- 聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,必须通过聚合根开始导航,聚合根是外部可以保持对它的引用的唯一元素;
- 只有聚合根才能使用仓储库直接查询,其它的只能通过相关的聚合访问。如果根实体被删除,聚合内部的其它对象也将被删除。
工厂 factories
- 工厂用来封装创建一个复杂对象尤其是聚合时所需的知识,作用是将创建对象的细节隐藏起来
- 一个单独的工厂通常生产整个聚合,传出一个根实体的引用,确保聚合的不变量都有
- 如果创建对象很简单,使用构造器或者控制反转/依赖注入容器足够创建对象的依赖,则不应该考虑使用工厂
仓储与工作单元 Repositories/Unit Of Work
- 仓储负责按照要求从数据库中读取数据以及把领域服务修改的数据保存回数据库。
- 聚合内的若干相关联的操作组成一个“工作单元”,实现事务的强一致性
- 聚合内的数据操作是关系非常紧密的,我们要保证事务的强一致性,而聚合间的协作是关系不紧密的,因此我们只要保证事务的最终一致性即可
- 仓储里面存放的对象一定是聚合,聚合作为一个整体概念,要么一起被取出来,要么一起被删除。
- 仓储分为仓储定义部分和仓储实现部分,我们在领域模型中定义仓储的接口,而在基础设施层实现具体的仓储。
- repositories本身是一种领域组件,但repositories的实现却不是领域层中的。
- respositories 与 dao 的关系:
- repository 与 dao 进行交互,并使用领域理解的语言提供对领域模型的数据访问服务的“业务接口”
- dao 是面向数据访问的,repository 是面向聚合根的
- dao 方法是细粒度的,更接近数据库,而 repository 方法的粒度粗一些,更接近领域。
服务 services
- 所有的 service 只负责协调并委派业务逻辑给领域对象进行处理,其本身并真正实现业务逻辑
- 通常应用中包括领域服务和应用服务:
- 领域服务(Domain Service)封装了领域概念,用于聚合内的业务逻辑
- 应用服务(Application Service)构成了应用程序或服务层,用于跨聚合协作以及聚合与外部系统协作的逻辑
- 应用服务协调多个领域服务、外部系统来完成一个用例
- 领域模型与外部系统不会发生直接交互,即领域服务不会涉及数据库操作
- 业务逻辑放入领域服务,而与外部系统的交互由应用服务来负责
- 领域服务与领域对象的区别:领域对象都是有状态和行为的,而领域服务没有状态只有行为。
领域事件和集成事件 Domain Events/Integration Events
- 领域事件:在同一个微服务内的聚合之间的事件传递。使用进程内的通信机制完成。
- 集成事件:跨微服务的事件传递。使用事件总线(EventBus)实现。
- 领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理
- 领域事件的触发点在领域模型(domain model)中,作用是将领域对象从对repository或service的依赖中解脱出来,当领域对象的业务方法需要依赖到这些对象时就发出一个事件,这个事件会被相应的对象监听到并做出处理
贫血模型与充血模型
- 贫血模型:一个类中只有属性或者成员变量,没有方法
- 充血模型:一个类中既有属性、成员变量,也有方法
- 面向对象设计主张将数据和行为绑定在一起,而贫血领域模型则更像是一种面向过程设计。
分层架构
概念
- 严格分层架构:某层只能与直接位于的下层发生耦合。
- 松散分层架构:允许上层与任意下层发生耦合。
- 在领域驱动设计(DDD)中采用的是松散分层架构
各层数据对象
- VO(View Object):视图对象,封装界面显示需要的值
- DTO(Data Transfer Object):数据传输对象,封装需要在不同层之间进行传输的必要字段
- DO(Domain Object):领域对象,抽象出来的有形或无形的业务实体
- PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系
传统三层架构的缺点
- 尽管由DAL,但仍然是面向数据库的思维方式;
- 对于一些简单的、不包含业务逻辑的增删改查类操作,仍然需要BLL进行转发
- 依赖关系是单向的,所以下一层中的代码不能使用上一层中的逻辑。
整洁分层架构
- 整洁架构与六边形架构、洋葱架构类似,每层的依赖很清晰,外层依赖内层。
- 达到领域层不受业务之外的其他因素干扰的效果。
洋葱架构
- 内层定义接⼝,外层实现接⼝
- 围绕独⽴的领域模型构建应⽤
- 依赖的⽅向指向圆⼼
- 所有的应⽤代码可以独⽴于基础设施编译和运⾏
六边形架构
- 外层依赖内层
- 端⼝就是接⼝,依赖接⼝编程