领域驱动设计

三种类型导向的设计:

DDD能解决的:

不能解决的:

核心概念

三者关系

领域

领域就是这个边界内要解决的业务问题域

子域

每个子域对应一个更小的问题域或更小的业务范围

模型

模型:对知识进行了选择性的简化和有意的结构化

有效建模的要素

stateDiagram-v2
  知识 --> 模型: 消化
  模型 --> 实现
  模型 --> 深层模型: 持续精进
  深层模型 --> 模型
  实现 --> 模型: 理解深入
  实现 --> 深层模型

通用语言

领域模型可以作为一种语言,语言就是模型的一种表现,模型与语言是一致的

修改代码就是修改模型,修改模型就是修改通用语言

通用语言的内容

使用文档与图作为面的面沟通的辅助手段,文档不应重复表示代码已经明确表达的内容,并且要与模型保持同步

模型驱动设计

在设计模型时需要考虑如何让代码更加自然地表达模型

亲自编码的建模师

如果参与建模的人最终没有参与到实现的工作中,那么模型与实现二者之间便会割裂开来

建模范式

模型驱动的设计并非一定只能使用OO设计,混用其他的一些设计范式能使设计更加自然

像现在我正在尝试系统整体使用OO,局部使用函数式

领域

分层架构

DDD中的四层

DD分层与六边形架构与整洁架构

层名 含义
用户界面层(或表示层) 负责向用户显示信息和解释用户指令。这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人
应用层 定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大,也是与其他系统的应用层进行交互的必要渠道。应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使它们互相协作。它没有反映业务情况的状态,但是却可以具有另外一种状态,为用户或程序显示某个任务的进度
领域层(或模型层) 负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的,但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心,这层理论上不应该直接依赖基础设施层,而是通过依赖倒置实现领域层对基础资源的解耦
基础设施层 为上面各层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件,等等。基础设施层还能够通过架构框架来支持4个层次间的交互模式

查询视图:

stateDiagram-v2
  state "Service(BIZ)(BO)" as Service(BIZ)
  Manager/Client --> 三方、中间件等
  Service(BIZ) --> Manager/Client: DTO/Param
  Service(RPC) --> Service(BIZ): DTO/Param
  Manager/Client --> DAO: Query/DO
  Service(BIZ) --> DAO: Query/DO/Param
  Controller --> Service(BIZ): Param/AO
  WEB/APP/其他服务 --> Controller: Param/AO
  WEB/APP/其他服务 --> Service(RPC): Param
  DAO --> DB

返回视图:

stateDiagram-v2
  state "Service(BIZ)(BO)" as Service(BIZ)
  三方、中间件等 --> Manager/Client
  Manager/Client --> Service(BIZ): DTO
  Service(BIZ) --> Controller: DTO/AO
  Controller --> WEB/APP/其他服务: VO/DTO/AO
  Service(BIZ) --> Service(RPC): DTO
  Service(RPC) --> WEB/APP/其他服务: DTO
  DB --> DAO
  DAO --> Service(BIZ): DO

常用的一些领域模型之间的转换工具类库:

领域模型

建模范式:

贫血模型:指领域对象里只有get和set方法(POJO),所有的业务逻辑都不包含在内而是放在Business Logic层

充血模型:拥有业务逻辑行为、数据封装

关联

代表领域中两个实体的关联 以及技术里的关联

为了使关联更易于控制:

  1. 使用单向关联
  2. 对关联进行限定,缩小范围
  3. 消除不必要的关联

实体

由标志所定义的对象,数据可变,拥有着自己的生命周期

这个标识是什么?是一个ID

实体类通常采用充血模型,与这个实体相关的所有业务逻辑都在实体类的方法中实现,跨多个实体的领域逻辑则在领域服务中实现

值对象

没有概念标识的对象

作为一个临时对象,应该是不可变的,通常用来传递消息

Service

有些操作是无法归类到某个值对象或者实体上面

需要使用Service来封装这些行为,由于上层的用户都可以使用Service, Service应用保持无状态

粒度更粗的Service更容易被服用,较粗粒度的Service也不容易把领域层的知识泄露到应用层上面去

模块

对一些职责类似的对象进行封装

领域对象的生命周期

stateDiagram-v2
  [*] --> 活动状态: 创建
  活动状态 --> 活动状态: 修改
  活动状态 --> 数据库表示: 存储
  数据库表示 --> 活动状态: 重建
  活动状态 --> 数据库或文件表示:存档
  活动状态 --> [*]: 删除
  数据库或文件表示 --> [*]:删除

聚合

聚合就是一组相关对象的集合,我们把它作为数据修改的单元。每个聚合都有一个根(root)和一个边界(boundary)。边界定义了聚合的内部都有什么。根则是聚合所包含的一个特定实体。对聚合而言,外部对象只可以引用根,而边界内部的对象之间则可以互相引用,聚合根的主要目的是为了避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题

聚合实例 Car是一个聚合根

聚合以及聚合根的构建:

stateDiagram-v2
  事件风暴 --> 找出实体和值对象
  找出实体和值对象 --> 找出聚合根
  找出聚合根 --> 将其他实体或者值对象归到某个聚合根
  将其他实体或者值对象归到某个聚合根 --> 定义聚合根之间的关系

设计原则:

  1. 聚合内的对象、实体具备自洽、不变性
  2. 聚合保持小而美
  3. 使用聚合根ID引用别的聚合根,而非直接的对象引用
  4. 聚合内数据强一致性,而聚合之间数据最终一致性
  5. 跨聚合的服务调用应有应用层来完成

工厂

当创建一个对象或创建整个聚合时,如果创建工作很复杂,或者暴露了过多的内部结构,则可以使用工厂进行封装。

创建方法要是原子的

工厂应该创建抽象类型 而不是具体类

Repository

客户需要一种有效的方式来获取对已存在的领域对象的引用 Repository是一个简单的概念框架,它可用来封装对聚合在数据库的检索技术

对类型进行抽象

充分利用与客户端解耦的优点

将事务的控制权交给客户

工厂与Repository的区别在于工厂聚焦于对象的创建,Repository的中间及结束生命周期,也就是查询已有对象

领域建模方法论

CBM

SOA

UML建模

事件风暴

围绕系统事件,定义事件,从而完成对系统的整体建模

参与者:项目干系人

材料:描述命令、实体、领域事件、补充信息的不同颜色贴纸

关注点:某些业务动作或行为(事件)是否会触发下一个业务动作,这个动作(事件)的输入和输出是什么?是谁(实体)发出的什么动作(命令),触发了这个动作(事件)

领域事件

一个领域事件将导致进一步的业务操作,在实现业务解耦的同时,还有助于形成完整的业务闭环

领域事件全流程

领域事件是实现 CQRS的一种方式

重构

为改进蹩脚的模型、实现更深层次模型而进行重构

所以DDD的重构不仅是为了干净的代码,也是为了一个更为一致的模型

重构的时机:

突破

持续重构能不断精进,当达到一定量促成质变,对模型的理解会产生一个突破,从而使模型更加自洽

当然这种突破也意味着大改,对项目来说是一种风险

提炼概念

将隐式概念转为显式概念

隐式概念

QueryWrapper qw = new QueryWrapper()
wq.equals("name", "cxk")
wq.like("address", "%翻斗大街%")

Specification 可以用来在验证、查询、创建用来描述必须满足什么规则

柔性设计

乐于使用 易于修改

使代码更加清晰可以表达领域模型,需要切分多个子域,从小开始,还要尽可能利用已有的形式来使现在与过去自洽

声明式设计*

把代码写成一种可执行的规则

也就说必须遵守某种预先定义好的规则

基于规则的编程

DSL是一种可以通过声明的方式完成业务的设计

使用分析模式

所谓分析模式,就是业务建模中的常见结构,可以理解是业务的设计模式

在重构精进时如果发现合适的分析模式,可以尝试使用,但重要的一点就是对于分析模式中的术语,应该忠于其原来的意义,不要赋予其不同的含义,因为分析模式的术语也是一种通用语言

使用设计模式

战略设计

保持模型的完整性

看似相同的概念其实并不是同一个东西,混用了不一致的模型导致软件的不正确性

模式:Bounded Context

限定模型的工作范围,通过封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义

模式:Continuous Integration

模式:Context Map

使用该模式描述两个边界上下文之间的关系

不同模式对相关系统的控制能力

合并上下文模式

单个界限上下文,两团队如一家人,紧密到无法分开。一切模型要素都是共享的。

模式:Shared Kernel

两团队之间紧密合作,代码模型可以提取成通用组件共享

模式:Customer/SupplierDevelopment Team

强依赖,上下游系统开发合作顺利,上游团队做出变更要对下游团队负责,使用自动化测试来验证来实现对模型逻辑修改反应迅速,能快速判断是否遵守契约

模式:Conformist

一定要跟某上游系统绑定,但别人是主,本系统是从。

模式:Anticorruption Layer

集成遗留系统,又不能强制老系统更新。新系统要做个适配器层来转换模型.

防腐层防止别人的模型影响到自身

模式:Separate Way
模式:Open Host Service
模式:Published Language

精炼

拆分组件,以便从某种形式提取出最重要的内容

模式:Core Domain

针对核心领域模型进行优化、开发,让高手们都集中在复杂的核心业务,打造领域知识沉淀的闭环

逐步精炼

模式:Gneric Subdomain

降低非核心领域模型的优先级,降低优先级意味着这些模块可以购买,可以被外包,反正不比核心领域受重视,资源也不会向这些模块倾斜太多

模式:Domain Vision Statement

简短描述领域模型及其它所带来的价值

模式:Highlighted Core

标记核心领域模型相关元素

模式:Cohesive Mechanism

当模型的某些行为变得复杂时 将这些行为抽离到一个独立的框架里

模式:Segregated Core

增强Core的内聚性

模式:Abstract Core

对核心领域进一步抽象 降低复杂度

大型结构

大型结构使得人们可以从宏观角度讨论及理解整个系统,使用这种结构可以使系统变得更清晰,通过重构来得到这些结构

模式:Evolving Order

让结构随着代码一起演变

模式:System Metaphor

一种促进系统一致性的隐喻,如防火墙之于网络领域,如建筑设计、城市规划之于架构领域

模式:Responsibility Layer

注意系统中的依赖 根据依赖可能会形成自然的层次结构 进而进行抽象成完整的层次结构

模式:Knowledge Level

利用约束来定制系统的行为,使得用户可以通过定义约束的形式来改变系统的行为

模式:Pluggable Component Framework

设计一个可插拔的灵活框架

使用误区