DDD阅读笔记(一)入门

354 阅读8分钟

最近阅读了info 的《DDD(领域驱动设计)-精简版》,整理了一些笔记用于后期回顾(不定期补充)。

原文档目录:

  • 构建领域知识
  • 通用语言
  • 模型驱动设计
    • 分层架构
    • 基本构成要素
      • 实体
      • 值对象
      • 服务
      • 模块
      • 聚合
      • 工厂
      • 资源库
    • 重构与关键概念
    • 模型一致性
      • 界定的上下文
      • 持续集成
      • 上下文映射
      • 共享内核
      • 客户-供应商
      • 顺从者
      • 防崩溃层
      • 独立方法
      • 开放主机服务
      • 精炼

一、开发流程

常见的开发流程:

graph TD
需求 --> 原型设计-->开发文档-->接口设计-->接口开发
需求-->开发文档

理想开发流程:

graph TD
0[需求]-->1[原型设计]-->2[<span style='color:red'>领域模型</span>]-->1[原型设计]
1[原型设计]-->开发文档-->接口设计-->开发
2[<span style='color:red'>领域模型</span>]-->接口设计

二、领域模型的重要性

在提出需求时,不同的需求对应的数据模型可能是相互冲突的,甚至存在逻辑冲突。如果在设计原型时,不考虑统一产品、前端、后端的领域模型,就容易导致返工与扯皮;甚至导致产品的顶层设计崩塌。

梳理领域模型并不意味着要在开发的流程中引入DDD的架构,梳理不是为了支撑架构,而是为了统一的模型实体,兼容不同需求。梳理领域模型,有利于设计更容易拓展、理解的系统。一个好的系统,可以完美的体现对应领域在实际世界的运转规律。

因此,开发人员也有必要了解DDD架构,并将领域的思想融入到开发中。

相关概念

  • MDD:模型驱动设计
  • MDA:模型驱动架构
  • 通用语言(Ubiquitous Language):使用模型作为语言的核心骨架,要求团队在进行所有的交流是都使 用一致的语言,在代码开发过程中也是如此。

核心思想

DDD的中心思想:

  • 关注精简的业务模型及实现的匹配。
  • 模型植根于领域、并精确反映出领域中的基础概念

思考的核心:

  • 如何体现对象关系
  • 如何去除无效关系降低系统复杂度,避免对象变更影响范围过大
  • 如何避免领域模型在开发过程中被肢解
  • 如何动手处理从模型到代码的 转换
  • 是否在项目中使用同一个模型

从领域到模型

如果尝试使用一张大图表示整个业务领域,那么复杂的业务概念会使得图的逻辑难以理解,更难落地。比较适合的方式是,通过肢解大图成小图,再聚焦到这些子集中,并将这些数据以及子集间的逻辑关系形成文档。

从领域到模型的转换方式如下:

graph TD
交流领域知识-->形成领域模型-->代码设计

领域知识 --> 蓝图--过滤-->领域模型
蓝图--完善-->蓝图

软件设计-->瀑布设计
软件设计-->敏捷方法

领域模型-->图
领域模型-->用例
领域模型-->画
领域模型-->图片

分层

经典的mvc模式将代码分为三层,而领域相关的实体会掺杂在服务层与数据层中。分层如下

  • 控制层
  • 服务层
  • 数据层

而DDD的架构将代码分为四层,开发每一层中内聚的设计, 让每层只依赖于它底下的一层。分层如下

  • 用户界面/展现层
  • 应用层
  • 领域层:保留业务对象的状态,只关注领域的构建
  • 基础设施层:作为其他层的支撑库存在。它提供了层间的通信,实现对业务对象的持久化

实际开发的分层关系如下

graph TD
领域层-.使用.->应用层
领域层--实现--> 基础设施层
基础设施层--引入--> 应用层

实体对象:拥有能够跨越系统的生命周期甚至能超越软件系统的一系列的延续性和标识符的对象,被称之为实体。当一个对象可以用其标识符而不是它的属性来区分时,可以将它作为模型中的主要定义
值对象:用来描述领域的特殊方面、且没有标识符的一个对象;这种对象不需要实例化。

三、领域服务

有些领域中的动作,它们是一些动词,看上去却不属于任何对象。我们不能只拥有一个单独的功能,它必须附属于某个对象。通常这种行为类的功能会跨越若干个对象,或许是不同的类。当这样的行为从领域中被识别出来时,最佳实践是将它声明成一个服务

在DDD的实践中,领域层可以将领域服务定义为接口,而基础设施层通过引入包并实现对应的服务。领域服务在业务上应该是不可分的原子状态。如果有某些业务节点在其他服务中会被调用,那么该业务节点需要定义为独立的领域服务,且服务应该是无状态的。

领域服务的特征:

  • 涉及一个领域概念,不属于一个实体或者值对象
  • 被执行的操作涉及到领域中的其他对象。
  • 操作是无状态
  • 操作是原子的、高内聚的,业务节点不会在其他领域服务中复用

提供的功能如下:

  • 操作业务
  • 转换领域对象
  • 领域对象的汇总与值对象的返回

服务的隔离

使用服务时,保持领域层的隔离非常重要,而在实际开发中,与领域的交互会导致边界模糊。

单个领域行为可以肯定是属于领域层的服务,那多领域应该算应用服务还是领域服务?这个边界在DDD架构实际落地时,是很难做区分的。一个应用服务中封装的领域行为不一定是高内聚的,可拆解的领域行为分开后不一定能组合成应用服务

四、应用服务

而在业务之外,服务还有许多基础架构问题需要考虑,这些逻辑有的存在于每个服务,有的存在于多个服务中中。例如:

  • 消息验证
  • 错误处理
  • 监控
  • 事务
  • 认证与授权

而这些逻辑一般在基础设施层中实现,在应用服务层组合

横切关注点

这些逻辑可以称之为服务的横切关注点,这类横切关注点职责内聚,但又分散在多个服务中。与“横切关注点”对应的是“核心关注点”,就是与系统业务有关的领域逻辑。例如订单业务是核心关注点,插入订单时的事务管理则是横切关注点。因此,我们可以总结出第一条应用服务设计的规则:与横切关注点协作的服务应被定义为应用服务

应用服务的边界如下:

  • 与横切关注点进行协作的只能是应用服务,若应用服务要与领域交互,则尽可能将与横切关注点无关的业务逻辑放到领域层中。

常见类型

  • 日志
    • 日志是最常见的非业务逻辑,而在DDD中应当作为基础设施层使用。但在实际开发时,一般会混杂领域层或应用层中,除非使用AOP的方式。而这取决于开发者关心代码的纯粹还是质量,如果专注质量就需要更完整的日志。
  • 验证
    • 如果验证属于业务逻辑的一环,那么应该属于领域层;否则是应用层
  • 分布式通信
    • 微服务缩小了架构设计上的物理边界,而分布式通信也成为了服务间的桥梁。
    • 从业务方面来说,分布式通信的调用是作为业务链的一环;从实现方面来说,又归属于基础设施
  • 异常处理
    • 与领域逻辑有关的错误与异常,应该以自定义异常形式表达业务含义,并被定义在领域层
    • 为包装应用服务的通用性,在应用服务中需要将业务异常转换成通用的异常,例如统一定义为ApplicationException
  • 通知
    • 通知关注点包括邮件通知、短信通知等功能,归属于基础设施层.
    • 与具体业务绑定
    • 除非通知服务自身可能与主业务强相关,否则应该放在应用服务.
  • 身份验证
    • 归属于基础设施层,实现方式都是被定义为一个filter,放到对Request进行处理的管道上

所以,应用层服务不应该包含业务,提供的功能如下:

  • 服务的编排与转发
  • 事务
  • 身份认证与授权
  • 参数校验

五、重构与关键对象

基于领域模型的重构

  • 方式
    • 阅读业务规范
    • 基于模式的技术性重构,可以被组织并结构。
    • 名词被转换成类,而动词则成为方法。
  • 流程
    • 打磨模型:基于对领域的深层理解以及对关注点的理解来细化它和设计
    • 提取隐式概念:作为描述其它概念的基本概念,在沟通时重复提取
  • 忽略表面内容且捕捉到本质内涵的模型是一个深层模型

相关概念

  • 约束:表示不变量的方法,不会因为对象的变化而变化。
  • 过程:表示对象的行为,一般作为服务的行为
  • 规约:测试对象是否满足特定条件

六、参考文档

如何分辨应用服务与领域服务
DDD-精简版