这是一种思想,不是一个工具。更多内容前往 IT-BLOG

一、领域驱动设计(DDD:Domain-Driven Design)

Eric Evans2004年提出的一种软件设计方法和理论。在应用架构的设计中,领域驱动设计DDD占据着非常重要的位置,可以说DDD是应用架构设计的核心。DDD是一套综合软件系统分析和设计的面向对象建模方法。

过去系统分析和系统设计都是分离的,正如“系统分析师” 和“系统设计师” 两种职称考试一样,这样割裂的结果导致,需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。

DDD则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。

二、DDD与数据驱动设计的区别

DDD给我们带来的是设计模式的改变。DDD的设计模式与传统的面向数据驱动的开发模式有明显的区别。

ddd设计java ddd设计工具_领域模型

【1】数据驱动设计从数据出发,先梳理E-R图(实体-联系图),设计数据库表结构,编写DAO,然后实现业务逻辑。数据驱动设计主要采用贫血模型[数据对象除了简单setter/getter方法外,没有任何业务方法],业务逻辑散落在大量的方法中,当系统越来越复杂时,开发和维护成本很高。
【2】DDD从领域出发,主要采用充血模型,分析领域内模型及它们之间的关系,并进行领域建模,设计核心业务逻辑,进而实现技术细节。通过DDD,定义领域模型,从而确定业务和应用的边界,保证业务与代码的一致性。

DDD最大的好处是:接触到需求第一步就是考虑领域模型,而不是将其切割成数据和行为,然后数据用数据库实现,行为使用服务实现,最后造成需求的首肢分离。DDD让你首先考虑的是业务语言,而不是数据。重点不同导致编程世界观不同。领域模型和数据模型是解耦的,有时也不是一一对应的,因此在应用DDD进行设计时,一定要摆脱数据模型优先的束缚,不要让领域模型被数据模型“绑架”,设计出合理的领域模型是首要任务。

DDD是解决复杂中大型软件的一套行之有效方式,在国外已经成为主流。DDD认为很多原因造成软件的复杂性,我们不可能避免这些复杂性,能做的是对复杂的问题进行控制。而一个好的领域模型是控制复杂问题的关键。领域模型的价值在于提供一种通用的语言,使得领域专家、产品经理和软件技术人员联系在一起,沟通无歧义。

三、DDD与微服务的关系

微服务提倡将应用划分成更细粒度的服务,服务之间互相协调、互相配合,为用户提供最终价值,是技术层面的分布式技术架构模式,是技术实现和部署的范畴。

DDD根据限界上下文设计出的领域模型和领域服务,通过微服务进行落地,并结合微服务及其他分布式技术(如DevOpsCI/CD、秒杀、全链路压测等),加速系统的落地。一个域服务可由一个微服务来实现,也可根据DDD领域分析拆分为多个微服务,对外集合成统一的域服务。

四、DDD常用的分析方法

DDD概念参考链接

DDD常用的分析方法主要有用例分析法四色建模法事件风暴法、领域故事讲述、用例法、战术设计、战略设计、洋葱框架、六边形架构、分层架构、CQRSCOLA等。

用例分析法

比较传统的需求调研过程中结合领域模型的设计思路进行,核心是通过业务需求、场景流程等梳理用例,进而规划领域模型。

用例分析的前提是业务架构的需求输入,其中核心是业务能力与业务流程

比如电商领域的订单寻源、库存锁定、商品价格计算、优惠券核销等业务能力,以及订单处理、分单和拆单、逆向退款等业务流程。

编写用例时要避免使用技术术语,应该使用最终用户或者领域专家可以理解的语言,进而我们可以基于用例分析法,根据语义来整理用例,然后整理领域模型,大概步骤如下。

ddd设计java ddd设计工具_java_02

收集用例: 从业务能力、业务流程、业务需求描述中进行提取,收集相应的名词、动词、形容词,以及对应的业务场景。
提取实体: 从名词中定位出主要实体,如商品、SKU、品类等。
提取属性: 从形容词中添加实体属性,如颜色、价格等。
添加关联: 从动词或形容词中添加实体和实体之间的关联,如商品“包含”SKU,卖家“开设”“多家”店铺等。
完善模型: 识别出初步模型,验证并迭代模型,同时补充用例验证模型、业务流程验证模型。

举个关于电商的例子:假设有这样的需求描述:“会员使用代金券兑换了很多促销的商品。”
我们先从名词“会员”“代金券”“商品”中提取实体,并从形容词“促销的”提取商品的属性,进而将动词“使用”“兑换”识别成关联,同时结合行业知识得知,代金券属于优惠券的一种,最终得出领域模型。

ddd设计java ddd设计工具_数据库_03

四色建模法

四色建模法在实践中也比较常用,其包括以下几个核心概念。

时间记录(Moment-Interval): 具有可追溯性的记录运营或管理数据的时刻或时段对象(如用粉红色表示)。
人、地、物(Party-Place-Thing Archetype,PPT): 代表参与到流程中的参与方、地点、物(如用绿色表示)。
角色(Role): 在时间记录与PPT对象(通常是参与方)之间参与的角色(如用黄色表示)。
描述(Description): 对PPT对象的一种补充描述(如用蓝色表示)。

简单地说,四色建模法关注的是,某个人(Party)的角色(PartyRole)在某个地点(Place)的角色(PlaceRole)用某个东西(Thing)的角色(ThingRole)做了某件事情(Moment-Interval)。

下面以一个课程报名缴费的例子对四色建模法进行说明:
报名人可以为学生进行报名,产生对应的报名登记记录和课程表,进而缴费人进行缴费,产生缴费记录。
在这个过程中,“人”有学生和课程,对应的“角色”是报名人和缴费人,完成的“时间记录”是报名登记记录、课程表及缴费记录,再加上一些补充描述。

ddd设计java ddd设计工具_ddd设计java_04

事件风暴法

事件风暴又称事件建模,与头脑风暴类似,可以快速分析复杂的业务领域,完成领域建模的目标。事件风暴是事件驱动设计的典型代表,是一种快速、轻量且未得到充分认可的群体建模技术,它对于加速开发团队非常适用。

事件风暴法关注以下元素:
事件: 发生了什么事情,产生了什么结果(如用橘黄色表示)。
属性: 事件的输入、输出,是对时间的细化描述。
命令: 某个动作的发起者,可能是人、外部事件、定时器等(如用蓝色表示)。
领域: 领域的聚合、内聚、低耦合,聚合内部保证数据的一致性(如用黄色表示)。

简单理解就是谁在何时基于什么(输入)做了什么(命令),产生了什么(输出),影响了什么(事件),最后聚合成了什么(领域)。

事件风暴催化并加速整个建模过程,强调正确的人(业务人员、领域专家、技术人员、架构师、测试人员等关键角色都要参与其中)、开放空间(有足够的空间可以将事件流可视化,让人们可以交互讨论)、即时贴(至少三种颜色),关联的人充分讨论,集体决策,从价值角度来审视业务流程的合理性。领域事件容易促使业务人员和非业务人员达成共识。

下面通过一个电商的例子说明事件风暴的主要过程:

【1】基于业务流程和业务流程的输入,对事件进行头脑风暴,主要识别应用层面的主要状态结果。比如识别出“商品已创建”,“库存已扣减”,“订单已支付”等。

【2】识别命令,即什么人做什么事,可以识别出运营人员可以添加商品和编辑库存,用户可以创建订单,并伴随着对应的事件。

【3】进行聚合,即将相关的实体聚合在一起,可以看到商品、库存、订单三个领域初步识别,并与相关的命令和事件结合在一起。

【4】对这些领域进行边界划分,识别出对应的限界上下文。

ddd设计java ddd设计工具_uml_05

五、DDD分层架构

DDD在具体落地实施的过程中,强调四层分层结构,将核心概念进行有效的整合,各层的职能定义如下。

ddd设计java ddd设计工具_ddd设计java_06

展示层(Facade Layer): 负责与不同用户和应用之间的交互协议和数据格式的转换,因此它又叫用户接口层。
应用层(Application Layer): 应用层是很薄的一层,负责展示层与领域层之间的协调,它是与其他系统的应用层进行交互的必要渠道,负责对领域层组件进行简单封装,例如事务、调用应用程序的任务。应用层要尽量简单,其服务及方法一般以用例为对应关系,通常一个用例对应到一个应用层的服务方法,方法中不包含业务规则或者知识,不保留业务对象的状态,只保留应用任务的进度状态,更注重业务能力或者业务流程的相关展示。主要通过调用领域层和基础设施层来完成协调。
领域层(Domain Layer): 领域层是DDD的核心,包含一些核心概念,如领域实体、值对象、领域服务、聚合,以及它们之间的关系。它主要负责表达业务概念、业务状态信息及业务规则,具体表现形式是领域模型。DDD提倡充血模型,即尽量将业务逻辑归属到领域对象上。
基础设施层(Infrastructure Layer): 基础设施层向其他层提供通用的技术能力,为应用层传递消息(如API网关等),为领域层提供持久化机制(如数据库资源、中间件交互等),屏蔽技术底座能力(如底层服务的健康度检查、配置参数等)及其他通用的工具类服务。

除了比较经典的四层分层架构,DDD还有一种松散分层架构,即端口适配器架构。

ddd设计java ddd设计工具_java_07

端口适配器架构通过划分内部和外部,系统由内而外围绕领域模型展开。领域部分位于最内层,应用程序包含领域模型和业务逻辑,对于外部而言,通过各种适配器进行上下文集成,包括数据持久化、第三方数据集成,同时基于依赖注入和Mock机制,适配器完成便捷的替换和模拟。

不论哪种分层架构,都遵循以下几个通用的DDD分层原则:
无环依赖原则: 组件的依赖关系中没有环路,如果出现,则需要打破循环依赖。
稳定依赖原则: 被依赖者应该比依赖者更稳定,同时组件的抽象程度应该与其稳定程度保持一致。一个稳定的组件应该是抽象的,这样便于扩展。
依赖倒置原则: 高层次的模块不应该依赖低层次的模块,它们都应该依赖抽象。抽象不应该依赖具体,具体应该依赖抽象。

六、实战

以渠道中心(一个微服务)作为例子来做领域模型设计,核心就是设计2个图,一个是战略设计图(宏观) ,一个是战术设计图(细节)。

领域战略设计图

战略设计图是从一个限界上下文的角度出发去分析业务场景。主要是宏观上的核心域、子域、实体关系图。demo如下图:

ddd设计java ddd设计工具_uml_08

领域战术设计图

战术设计图是从一个限界上下文的角度出发去分析业务场景。细化到核心业务字段、领域实体、值对象、领域服务、领域事件等等。基本上这个图画完,代码已经知道怎么写了。demo如下图:

ddd设计java ddd设计工具_uml_09

技术实现

整体项目框架分层图如下所示:

ddd设计java ddd设计工具_数据库_10

4层典型DDD分层结构:
【1】展现层: controller层,无业务逻辑。
【2】应用服务层: 此层可以包含查询逻辑,但核心业务逻辑必须下沉到领域层。
【3】领域服务层: 业务在这里组装。仓储(资源库)接口在此层定义。
【4】基础设施层: 仓储(资源库)实现层+PO持久化层。

服务调用问题

域内调用: 领域内调用,随便调用,丝般顺滑。至于实现,可以由一个核心域的仓储实现层(第四层)去实现多个Repository接口。(比如这里A是核心域的实体名,B是支撑域、通用域等)

ddd设计java ddd设计工具_数据库_11

跨域调用: 使用领域事件eventbus来做解耦,考虑是否有可能合并为一个领域。跨上下文(肯定跨域):ACL层->Adapter适配器层->feign调用

ddd设计java ddd设计工具_ddd设计java_12

包结构

包结构如下:

ddd设计java ddd设计工具_java_13

展开包结构如下:

ddd设计java ddd设计工具_java_14

展现层: Controller仅做接口的入口定义和编排转发,不做任何的业务处理。
应用服务层: application负责接口参数DTO的简单校验,以及DTO和实体值对象的数据转换,对于简单的业务,也可以在应用层加载实体直接执行实体行为方法。

领域层
模型: 根据领域模型分析领域内各实体、聚合、聚合根、值对象等,这些对象在*.domain.model定义,实体内的行为方法只负责维护实体自身的生命周期和状态。
行为: 领域内各实体、聚合、聚合根等,会有相应的行为,在*.domain.model包下定义行为方法。
领域服务: 领域提供的接口服务,需要定义在*.domain.service包下,业务相关的前置业务判断、多个实体或值对象的行为逻辑处理等,都在领域服务中实现,需要注意的是并不是每个实体都有一个对应的领域服务,但是依赖多个实体的行为方法,最好根据这个业务模块是建立一个领域服务。
仓储: 领域服务或上层应用服务需要使用到的基础设施层,包括DBFeign调用等,定义在*.domain.repository下,在*.infrastructure.repository下实现。
适配层:acl包下的feign定义依赖外部的接口,并在acladapter包编写转换,由仓储层操作实体时调用。

持久层: 与常用DAO定义一致,由仓储层操作实体时调用。