DDD理论到实践

420 阅读8分钟

这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战

这期打算斌带着问题边进行学习归纳

本文主要分为以下几个部分

  • DDD基本信息
    • 什么是DDD
    • 适用场景
  • DDD主要概念
    • 实体、值对象
    • 充血模型、贫血模型
    • 领域及其子概念
    • 聚合、聚合根
    • 防腐层
    • 领域事件
  • DDD战略设计
    • 产品愿景
    • 功能分析
    • 领域建模
  • DDD战术设计
    • 领域对象分析
    • 项目结构设计
    • 领域以及领域对象组织
    • 代码目录设计
  • DDD误区

在项目的重构中,我们团队也用到了DDD的思想进行项目的设计,在这个过程中也产生了不少的疑问与心得

疑问:

  • 当实体A中的某个属性是实体B的列表,同时实体A实体B都可以独立存在、独立使用,实体A、B是属于同一个领域还是属于不同的领域?
  • 部分逻辑较为独特,不属于复用性强的逻辑,和某个服务层方法强绑定,但是又只和单一领域相关,那么它应该放在对应领域层还是服务层?
  • 如何有效区分领域对象方法(实体方法、值对象方法)与领域方法?
  • 领域层之间的相互依赖怎么实现?
  • repository层的相互依赖怎么实现?
  • 业务并没有那么复杂,需要完整实现整个DDD结构吗?

一些心得:

  • DDD可以清晰地划分各个业务领域,提供微服务划分指导
  • DDD可以根据实际的场景进行调整
  • 更多的是需要与团队达成共识
    • 形成统一的项目结构共识
    • 共同形成代码风格上的一致,使得项目本身可以更加健康的长期维护

DDD基本信息

什么是DDD?

领域驱动设计,面对复杂业务,清晰划分业务边界, 解耦业务,达到业务模型与代码模型保持一致

解耦业务意味着可以指导微服务的划分

适用场景

业务较为复杂,模块概念较多

微服务边界划分困难

DDD主要概念

实体、值对象

实体和值对象是领域服务中的业务对象

两者都拥有自己的对象方法

实体:

  • 承载领域服务、拥有主要属性的类
  • 有明确的身份信息
    • 即使属性一样,只要身份标识(如ID)不一样,也就不一样

包含实体属性与方法:

  • 方法只关心自身变化
    • 实体与其他实体的之间的业务逻辑应该在领域层
  • 通常采用充血模型

值对象:

  • 没有明确的身份信息,或者说身份信息没有意义
    • 只关心其属性值,如果都相同,那么这两个值对象相同

也有自己的类方法与属性:

  • 通常为贫血模型

贫血模型与充血模型

充血模型:

只包含属性(业务数据)不包含方法

充血模型:

不仅仅包含属性(业务数据),还包含了该对象的自身的业务逻辑

典型的面向对象编程

领域及其子概念

领域即业务边界、业务的功能范围

根据重要程度以及功能划分以下三种:

  • 核心域:业务核心模块
  • 通用域:具有一定的通用性的服务
  • 支撑域:具有一定的特殊性,但是是基础支撑服务

领域层不关心下一层的Repos层的实现以及数据持久化形式

聚合以及聚合根

聚合:

将领域内的实体、值对象进行组织向外提供服务

  • 实体采用充血模型,实现实体个体业务逻辑
    • 包括实体职能
  • 跨实体业务逻辑用领域服务实现(于聚合内)
  • 跨领域的业务逻辑于应用服务层(application层)实现

聚合高内聚、低耦合

多个聚合(领域)形成界限上下文,一个界限上下文实际上是一个application,为微服务的划分边界

聚合根

为实体,是该领域中的主要业务对象,是该聚合的管理者

  • 该聚合(领域)的任何操作都应该经过管理者之手

防腐层

在我的理解中,就是一种转换

可以是任何带有转换作用的东西

  • 可以是项目中不同层之间对象转换的代码
  • 可以是微服务之间传输对象与应用服务对象的中间代码
  • 可以是新旧服务迁移,兼容数据转换的代码
  • 可以是同作用、来源不同且异构的获取、转换代码
  • 部分上述场景中的代码可以换成服务

领域事件

领域之间可能会有协作与相互影响关系

从根本上说,实际上是对于特定领域来说,发生某件事情会影响自身的状态(触发某些事件)

也就是领域会受到事件影响

  • 采用事件机制实现
    • 处于/不处于同一个微服务的领域实现事件机制的方式可能不一样
      • 同一微服务:事件总线
      • 不同微服务:共享资源
        • 消息中间件
        • 轮训数据库状态
        • 监听binlog
        • 事件是否需要持久化?
        • 事件失败
          • 事件重试
          • 事件幂等
          • 事件补偿
        • 采用多个共享资源的时候,可能存在一致性问题

DDD战略设计

侧重模型分析、业务分析,建立整个DDD模型

产品愿景

需要开发人员、领域专家、需求方、产品经理、架构师坐在一起事件风暴,确认以下几个点

  • 项目目标是什么
  • 项目的目标用户、核心价值、差异化竞争点

场景分析

为了探索产品中的主要场景、操作用例

从而达到指导建立领域模型的目的

领域建模

  • 确认实体、值对象等领域对象
  • 寻找聚合根
    • 在同一个业务范围内紧密合作的实体值对象
    • 找到该业务范围内的主导实体,为聚合根
    • 允许无聚合根,多实体共同完成领域内业务,采用传统方法进行实体管理
  • 确定聚合(确定领域)
    • 确定聚合方法(领域方法)
  • 确定界限上下文(application)
    • 如何划分界限上下文?
    • 同个界限上下文可以划分出多个application
      • 如何拆分application?
        • CQRS:将命令(会影响数据的操作)与查询(数据无影响)操作划分到不同application
        • 根据操作目的的主要业务实体进行划分
          • 如求一个工资单内所有工资条的实发金额的和,主要目的业务实体为工资单
        • 根据操作人身份划分
    • 确定application层方法

DDD战术设计

侧重模型落地,考虑模型内部实现细节

领域对象分析

  • 确认每个领域对象应该是实体还是值对象
    • 确定对象属性以及方法
  • 确定领域方法实现
  • 确定application层方法实现

对象定义

application层:

  • AppService:应用服务(构成应界限上下文)
  • DTO/Model:data transform object,暴露给外部的传输对象

domain(聚合)层:

  • DomainService:领域服务,该聚合的服务体现,管理DO、VO,包含Repos属性
  • DO:domain object,领域实体
  • VO:value object,值对象
  • Repos:仓储层Interface抽象

Persistent(仓储层):

  • ReposImple:仓储层具体实现,仓储层给上层的是DO,转换可能会依赖多个库(DAO层)
  • PO:Persistent Object,数据库表结构对应对象
  • DAO:数据库表对象方法对应实现

对象关系:

实线框:对象 密虚线框:独立方法 疏虚线框:DDD概念范围

实体间的包含关系通过依赖注入的方式进行初始化

实体只能使用自己内部的实体、基础工具

项目结构设计

调用关系以及协作

调用层级关系:

含有跨领域(聚合)的调用关系:

DDD误区

所有地方都要DDD

  • DDD适合用于业务逻辑较为复杂的业务
  • 需要组内成员对DDD有所心得,并且有使用上的共同约定

完全按照DDD进行战术设计

战术设计和战略设计不一样的是

战术设计是战略设计的落地实现,实现方式有很多,可以采取DDD这种划分领域层存储层、划分实体、聚合管理实体值对象、通过事件进行业务影响扩散等

如果业务较为简单,也可以直接像传统方式,直接CURD即可

重战术轻战略

战略是战术基础,战略不清晰,战术易重构