DDD理解与思考

DDD与业务场景分层建模思考

本文会围绕DDD领域驱动设计,对于实际业务场景分层建模的应用进行介绍

何为DDD

简介

DDD是领域驱动设计的简称(Domain Driven Design),他是用来解决复杂软件系统设计的一套建模思想,旨在将复杂系统拆分的分治思想。以业务范围为中心,将不同业务划分为不同领域模型,自顶向下的进行系统的解耦;

对于整个系统,其每一部分业务都是不断变化的,而将业务抽象为领域模型能更好的进行业务的拓展和升级,也能更安全的控制每个业务域的稳定性。对于具体某个业务域,又能将其更加细致的分为多个子域,每个子域又能维护本身的各个实体与边界。所以DDD也是更深层次的面对对象的思想。

综上,DDD的核心思想有:

  • 领域

    将某个业务的所有业务相关的规则,定义等抽象成一个整体的概念;例如电商系统中,用户下单相关的业务场景就能划分为订单域,支付的业务场景可以划分为交易域,售后服务相关的能划分为客服域。

  • 分治

    将一个大的系统通过业务场景的不同划分为不同的领域,每个领域单独治理维护,将复杂的系统结构分解成更小的一个个业务单元,能够更好的进行业务功能的迭代升级与维护,也极大的降低了系统的耦合。

DDD与MVC

mvc

经典的MVC模式中,将应用分为三层,Controller层负责业务逻辑的处理,Model负责与数据库持久层的数据模型,View负责网页端数据的展示。可以看出MVC模式各个模块之间有着清晰的责任属性,对于业务简单的系统可以有很高的效率。但当业务内容的增多,业务场景 的多样化,MVC模式会愈发臃肿,各个功能模块之间相互调用错综复杂,大大增加的系统的维护难度,影响整个系统的稳定性。

由此,可以清晰的看出DDD模式对于复杂系统的优势

  • 系统解耦,降低系统的复杂度

    DDD将系统以业务范围进行划分,每个业务域有严格的边界,避免了不同业务域之间互相影响

  • 分治思想,业务域独立维护

    将业务行为与数据结合,每个业务域只需着重于本域的业务功能实现

  • 便于拓展,使系统能够稳定发展

    业务功能模块化组成,新增业务场景只需增加新的业务域模块,不会影响整个系统原有的功能模块

  • 提高代码复用,增强隔离性

    一些通用的业务域可以为其他的业务域提供统一的服务,并且拥有较强的隔离性

DDD的知识体系

DDD的设计大体可以分为战略设计和战术设计

zlzs

战略设计

战略设计又叫战略建模,是将业务拆分建模的过程

从业务视角出发,对业务需求进行拆分,划分子域,限界上下文,通过领域语言进行领域划分以及构建领域模型。并在构建领域模型时梳理出业务对应的聚合、实体、以及值对象

主要概念有:通用语言、领域、子域、界限上下文

通用语言

定义上下文的主要含义,具体域的主要业务内容,团队达成共识的,能清晰准确地描述业务含义和规则

unionLang

通用语言贯穿 DDD 的整个设计过程,作为项目团队沟通和协商形成的统一语言,这里的团队不仅为技术团队,也包含业务、运营、产品等,通用语言必须要限定在某个上下文内,以确保每个上下文含义在它特定的边界内都有唯一的含义

领域

领域是值业务的特定范围,涉及业务的规则、概念、流程等,是对业务问题分解组织的基本单位

lingyu

DDD会将大的业务问题,根据业务概念进行划分,而领域则是划分的特定范围内的业务模型

子域

子域则是领域内,根据具体的业务规则和功能需求划分的更小的一个独立的 业务/功能 模块

子域主要是将大的系统分解化简,降低整个系统的复杂度以及耦合

子域又分为:

  • 核心域:主要的业务功能
  • 通用域:与业务功能无关的通用功能,能够被多个子域共享,包含一些通用的服务,工具等
  • 支撑域:主要为核心域的功能实现提供定制化的功能服务辅助,对于核心业务起到重要作用,与核心业务具有强相关性

界限上下文

界限用于划分和隔离不同领域,是领域的边界,而上下文则是领域的语言环境

用来封装通用语言和领域对象,提供上下文环境。这个边界定义了模型的适用范围,每个上下文内部有自己的语言和模型,他们之间通过明确定义好的接口进行通信(RPC,HTTP,MQ等方式)


战术设计

具体的功能逻辑的设计与实现

战术设计也称为战术建模,从技术视角出发,以领域模型基础,进行微服务拆分,在每个微服务中进行领域分层,实现领域服务,从而实现领域模型对于代码映射目的,最终实现DDD的落地实。

主要概念:实体、值对象、聚合、聚合根、资源库、领域服务、领域事件

zhanshu

实体(Entity)

有唯一的身份表示和生命周期的对象,有自己的行为状态

与传统数据模型设计优先不同,DDD 是先构建领域模型,针对实际业务场景构建实体对象和行为,再将实体对象映射到数据持久化对象

实体在聚合中唯一,且生命周期由聚合根管理,且实体一般会持久化,于数据持久化对象有着一对多,多对多的关系

实体常见的展现形式有:

  • 贫血模型

    指实体对象内只具有数据属性,而缺乏业务逻辑的行为方法。其业务逻辑主要存在于服务层和其他外部对象中,实体对象仅为被动的数据容器。这种模型有悖于DDD的设计思想,其无法体现领域的概念于规则,常见于MVC的设计中

  • 充血模型

    指实体对象内充分包含了业务逻辑的行为方法,其明确的表示出了数据和行为逻辑的关系,能更好的封装与实体对象相关的业务规则和方法,并且提供统一的接口。这种模式是DDD设计思想的体现,能使得实体对象成为业务规则的中心

关于两种模型的选择,没有确切的答案,充血模型虽然更加体现DDD的思想,但如果只使用充血模型。则一些复杂的业务操作也会包含在实体中,使得实体愈发臃肿,且在团队开发中,对于哪些功能下放到服务层,哪些功能保留在实体中无法有明确的定义。而只使用贫血模型则又会使实体显得单薄,无法更好的维护自身的业务功能。一般看来,实际开发中常会使用 贫血+充血模型的组合模式,将复杂的以及于其他实体交互多的方法下放到服务层实现,而实体保留一部分功能单独维护。

总而言之,模型没有好坏,好的设计需要团队共同探讨实现,并且要适合于实际的业务场景

值对象

没有唯一表示的对象,通过自身属性的值来判断相等性,是多个相关属性组成的一个数据整体

值对象只是若干个属性的集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立出来了,但在逻辑上它仍然是实体属性的一部分,用于描述实体的特征

实体和值对象是微服务底层的最基础的对象,一起实现实体最基本的核心领域逻辑。值对象和实体在某些场景下可以互换,值对象在某些场景下有很好的价值,但是并不是所有的场景都适合值对象。

DDD 提倡从领域模型设计出发,而不是先设计数据模型。传统的数据模型设计通常是一个表对应一个实体,一个主表关联多个从表,当实体表太多的时候就很容易陷入复杂的数据库设计,领域模型就容易被数据模型绑架。值对象的诞生,在一定程度上,和实体是互补的。

聚合

一组相关对象的集合,由一个根实体作为入口,管理内部对象的生命周期和完整性

领域模型内的实体和值对象就好比个体,而能让实体和值对象协同工作的组织就是聚合,它用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性

聚合是数据修改和持久化的基本单元,每一个聚合对应一个仓储,实现数据的持久化

聚合实现了微服务的重要特征:高内聚,低耦合

  • 聚合有一个聚合根和上下文边界,这个边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。
  • 聚合是微服务内的逻辑边界,便于微服务以聚合为单位的拆分和组合
  • 聚合可以是一个单独的微服务,也可以由多个聚合组成一个微服务

聚合根

是聚合的实体根,和入口点,管理着聚合内其他对象的生命周期和完整性,通过封装聚合内的对象定义了聚合的一致性边界

聚合根则是组织的负责人,聚合根也叫做根实体,它不仅仅是实体,还是实体的管理者,在聚合内部,负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致性的问题。

  • 作为实体,聚合根具备自己的业务属性,业务行为,业务逻辑

  • 聚合之间,是聚合对外的对接人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同

  • 如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体

聚合根在聚合内对实体和值对象采用直接对象引用的方式进行组织和协调,聚合根与聚合根之间通过 ID 关联的方式实现聚合之间的协同

juhe

领域事件

领域事件是解耦微服务的关键,表示领域中事件的发生,用于通知其他领域对象跨界限上下文进行协作

领域事件可以切断领域模型之间的强依赖关系,事件发布完成之后,发布方不必关系订阅方事件处理是否成功,这样可以实现领域模型之间的解耦,维护领域模型的独立性和数据的一致性。在领域模型映射到微服务系统架构时,领域事件可以解耦微服务,微服务之间的数据不必要求强一致性,而是基于事件的最终一致性

领域事件又分为,微服务内与微服务之间两种

处理流程包括事件构建和发布、事件数据持久化、事件总线、消息中间件、事件接收和处理等

lyevent

领域服务

代表领域中的一些业务逻辑操作,为了解决特定领域问题提供通用的服务

领域服务通常指相对聚焦的底层支撑域/通用域服务

领域服务表示一个无状态的操作,它用于实现特定于某个领域的任务。

当领域中的某个操作或转换过程不是实体或值对象的职责时,此时便应该将该操作放在一个单独的接口,即领域服务

资源库

用于管理领域对象的更新,和持久化的接口

资源库实现了将领域对象的持久化过程,以及从持久化介质中检索对象并还原成领域对象的功能

资源库封装了底层的数据访问细节,提供一致的接口和抽象,使领域对象的访问和持久化变得统一

分层架构

DDD 分层架构有一个重要的原则:每层只能与位于其下方的层发生耦合

分层架构可以简单分为两种,即严格分层架构和松散分层架构。

严格分层架构中,某层只能与位于其直接下方的层发生耦合,而在松散分层架构中,则允许某层与它的任意下方层发生耦合。

fcjg

分层架构的优点和明显:开发人员可以只关注整个结构中的某一层,更易于更新和替换层次的实现。且降低了层与层之间的依赖,有利于标准化,增加了各层逻辑的复用。

缺点例如:因为增加了中间层,降低了系统的性能,不过可以通过缓存机制来改善,可能会导致级联的修改。这种修改尤其体现在自上而下的方向,不过可以通过依赖倒置来改善。

六边形架构

实际上它也是一种分层架构,分为内部和外部。六边形架构又名“端口-适配器架构”

lbx

六边形架构将系统分为内部(内部六边形)和外部,内部代表了应用的业务逻辑,外部代表应用的驱动逻辑、基础设施或其他应用。内部通过端口和外部系统通信,端口代表了一定协议,以API呈现。一个端口可能对应多个外部系统,不同的外部系统需要使用不同的适配器,适配器负责对协议进行转换。

领域建模

领域建模的目的就是:提炼业务知识,形成统一语言,沉淀领域模型

领域建模直接影响着整个设计的成败,需要建立清晰的领域边界,标准规范,统一语言等

领域建模需要确定的内容有

  • 领域模型
    • 包含领域对象,属性,行为,关系,边界等各个方面,用于描述业务本质
  • 数据模型
    • 包括实体关系模型,数据库模型等,用于描述系统的数据结构和关系
  • 架构模型
    • 包含整个系统的物理以及逻辑结构,如组件、模块、接口等

事件风暴

是一种快速探索,理解,设计领域模型的方式,通过团队协作以用户的视角探索整个业务流程

sjfb

一种基于工作坊的DDD实践方法,可以快速发现业务领域中正在发生的事件,指导领域建模及程序开发

事件:即事实,即在业务领域中那些已经发生的事实就是事件
风暴:运用头脑风暴会议进行领域分析建模。

参与者:需求分析过程所需的人,应该覆盖客户(如有可能参与)、 业务人员、产品人员、架构人员、开发人员和测试人员。需要邀请各种角色,是因为每种角色都是整个项目成功的重要参与方, 而且具有不同的知识背景。有了不同的视角,事件风暴才会更有洞见。

组织形式:事件风暴遵循如下的结构:

  • 列出和业务流程相关的业务事件
  • 思考业务流程中的异常路径,即何种情况下会导致业务流程出现问题
  • 围绕着领域事件,思考是谁的何种业务动作导致了这个业务事件
  • 补充和发现其他的细节
  • 从前往后再次进行业务流程的走查,发现遗漏

便利贴:不同颜色的便利贴表示不同的类型和关注点

箭头:表示事务之间的关系,依赖或者先后顺序


四色建模

利用颜色作为可视化技巧,用于领域模型中表示不同的对象和概念,用颜色更清晰的理解和传达模型的结构关系

ssjm

我们将抽象出来的对象分为四种原型(archetype)

  • 业务关键时刻(Moment-Interval 简称 MI)
    • 表示事务在某个时刻或某一段事件内发生的,如一次下单,一次询价等,这样的对象一般有一个起始时间和终止时间,以及一个唯一的标识号,用来唯一的标识这一次用户请求,比如OrderNo
    • Ml是最重要的一类对象,因为他是系统业务价值所在,一般用红色表示
  • 角色(Role)
    • 抽象了一种参与方式,往往由人或者物来承担,会有相应的责任和权利,一般一个MI会关联多个Role
      如客户,商家,财务组织等
    • 这类对象是除moment-interval对象外最重要的一类对象,一般用黄色表示。
  • 人-事-物(Party,Place, or Thing 简称 PPT)
    • 表示扮演不同角色的人或者事物,如店铺,商品,账户
    • 一般用绿色表示
  • 描述(Description 简称 DESC)
    • 对上述颜色表示内容进行解释,用于描述建模中产生的数据,事件等
    • 一般用蓝色表示

概括来说,就是一个什么样的人或物品以某种角色在某个时刻或某段时间内参与某个活动。

其中:

  • 什么样的 -> DESC
  • 人或物品 -> PPT
  • 角色 -> Role
  • 某个活动 -> MI

例如在一个电商场景中的四色建模:

ssjmdemo

  • 首先发现业务关键时刻 ,建立整个领域模型的骨干(MI)
  • 得到骨干之后,要丰富这个模型,使它更好的描述业务概念,需要补充一些实体对象(PPT)
  • 在这个基础上,可以进一步抽象这些实体在事务中的角色(Role)
  • 最后再把一些需要描述的信息放入描述对象(DESC)

DDD理解与思考
https://kongke7.github.io/2024/08/09/DDD理解与思考/
作者
Kongke
发布于
2024年8月9日
许可协议