尼恩说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如有赞、阿里、滴滴、极兔、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:

谈谈你的DDD落地经验?

谈谈你对DDD的理解?

如何保证RPC代码不会腐烂,升级能力强?

微服务如何拆分?

微服务爆炸,如何解决?

你们的项目,DDD是怎么落地实操的?

所以,这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”

也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典PDF》V134版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,后台回复:领电子书

除了本文,尼恩输出了一个 《从0到1,带大家精通DDD》系列,帮助大家彻底掌握DDD,链接地址是:

阿里DDD大佬:从0到1,带大家精通DDD

阿里大佬:DDD 落地两大步骤,以及Repository核心模式

阿里大佬:DDD 领域层,该如何设计?

极兔面试:微服务爆炸,如何解决?Uber 是怎么解决2200个微服务爆炸的?

阿里大佬:DDD中Interface层、Application层的设计规范

字节面试:请说一下DDD的流程,用电商系统为场景

DDD如何落地:去哪儿的DDD架构实操之路

DDD落地:从腾讯视频DDD重构之路,看DDD极大价值

DDD落地:从美团抽奖平台,看DDD在大厂如何落地?

美团面试:微服务如何拆分?原则是什么?

DDD神药:去哪儿结合DDD,实现架构大调优

DDD落地:从网易新闻APP重构,看DDD的巨大价值

DDD落地:从阿里单据系统,看DDD在大厂如何落地?

大家可以先看前面的文章,再来看本篇,效果更佳。

另外,尼恩会结合一个工业级的DDD实操项目,在第34章视频《DDD的学习圣经》中,给大家彻底介绍一下DDD的实操、COLA 框架、DDD的面试题。

DDD现在非常火爆,是有其巨大生产价值,经济价值的, 绝不仅仅是一套概念那么简单。

DDD的绝大价值,具体请参见以下视频:

从腾讯视频DDD重构案例,看看DDD极大价值

本文目录

- 尼恩说在前面

- 一、有赞教育线索资源管理项目背景

- 二、领域驱动基础概念介绍

  - 2.1 领域驱动设计标准分层架构

    - 2.1.1 基础设施层

    - 2.1.2 领域层

    - 2.1.3 应用层

    - 2.1.4 用户界面层

    - 2.1.5 线索管理应用工程结构简单介绍

  - 2.2 需求分析利器 — 四色原型图

  - 2.3 DDD 几个核心领域概念

    - 2.3.1 实体

    - 2.3.2 值对象

    - 2.3.3 聚合

    - 2.3.4 仓储

- 三、线索资源管理 DDD 实战

  - 3.1 场景分析提炼四色原型图

  - 3.2 领域模型中实体/值对象/领域服务/聚合识别

- 四、总结与思考

- 说在最后

- 部分历史案例

一、有赞教育线索资源管理项目背景

作者:程英杰,有赞教育

在教育行业中,业务流程涵盖了招生开发、潜在学员信息管理、教务调度、学员沟通、互动辅导以及口碑传播。首先,在招生开发阶段,通过网络营销或线下推广活动收集潜在学员信息,并将其纳入信息管理系统。在潜在学员信息管理环节,利用信息资源管理系统对收集的数据进行统一管理,并将潜在学员转化为实际学员,为后续教务调度提供数据支持。显然,潜在学员信息管理在教育行业中起着承前启后的作用,其重要性不言而喻。

整个项目的业务场景如图 1-1 所示,项目分为两大业务领域,即线索领域和配置中心领域。线索领域主要负责线索收集、线索信息管理等职责;配置中心领域则负责整合公共配置资源,如线索相关的标签、来源等。

图片

图 1-1 线索管理业务总览图

在实际项目中,我们可以根据需求复杂程度和业务特点,灵活运用四色原型图、用例图、时序图等工具,结合 DDD 思想,全面、准确地描述业务需求,构建符合业务需求的优秀领域模型。只有经过严谨的分析和设计,我们才能开发出高质量的软件系统,为教育行业的发展贡献力量。此外,在项目实施过程中,还需关注潜在学员信息管理的实时动态,以便为招生开发、教务调度等环节提供准确的数据支持,确保整个业务流程的顺畅进行。

二、领域驱动基础概念介绍

在介绍 DDD 相关概念前,我们先探讨一下为何要采用领域驱动设计。在非领域驱动设计的项目中,我们通常会先进行数据库表的设计,然后根据表结构推导出相应的实体对象。这些实体对象仅仅是数据的载体,缺乏实际的行为。在这种设计模式下,业务流程实现仍属于面向过程,以数据为中心的过程式思想,开发过程可以理解为对数据进行移动、处理和实现的过程。而如果采用 DDD 的思想去设计,我们将构建一个基于面向对象原则的系统。接下来,我先介绍 DDD 的标准分层架构,然后介绍下需求分析阶段非常有用的四色原型分析模式,最后简要介绍下方案设计阶段常用到的几个 DDD 领域概念。

2.1 领域驱动设计标准分层架构

当前,业界较为通用的 DDD 架构采用的是四层模型,从下到上依次为基础设施层、领域层、应用层和用户界面层。具体的分层架构见图 2-1。

图片

图 2-1 领域驱动架构模型

2.1.1 基础设施层

基础设施层主要负责为其他层提供通用技术能力,如消息发送、领域持久化等。在实际项目应用中,这一层主要处理数据持久化操作,将领域对象序列化到各种存储介质中,如数据库、Hbase、MongoDB、ES 等,并从这些存储介质中读取数据,组装成领域对象。这一层通常采用仓储机制实现领域持久化能力。

2.1.2 领域层

领域层,也称为模型层,是业务系统中最核心的一层,几乎所有的业务逻辑都在这一层实现。领域层主要包括领域模型和领域服务。

(1)领域模型

领域模型用于抽象复杂的业务逻辑,将其转换为便于理解的概念图模型,一般由实体和值对象构成。它与数据模型的区别在于:数据模型描述的是对象的持久化方式,而领域模型表述的是领域中各个类以及各类之间的关系。

(2)领域服务

领域服务可以认为是领域模型的一种补充,因为在实际建模过程中,一些概念本质上是一些操作,涉及多个领域对象,并需要协调这些对象完成操作。若将这些操作硬性归类到某个对象,可能导致对象职责不明确。此时,就需要领域服务来承载这些操作,串联多个领域对象。例如,在线索管理项目中,线索详情页信息包括“线索基础信息”、“标签信息”、“来源信息”和“线索处理日志信息”。在建模时,我们考虑到合理性将这四者定义为四个单独的实体。然而,在获取线索详情时,如何整合完整的线索信息成为一个问题。为了解决这个问题,我们引入了领域服务,负责承载线索信息聚合操作。

2.1.3 应用层

应用层负责提供应用服务,主要负责业务用例的编排和组装。与应用层的主要区别在于是否处理业务逻辑。应用层主要协调领域层与用户界面层之间的关系,对外提供各种应用功能,对内调用领域层的领域对象或领域服务完成业务编排和组装。

2.1.4 用户界面层

用户界面层主要负责展示用户信息。具体来说,就是请求应用层获取所需展示的数据,并发送命令给应用层,要求其执行特定用户命令。在实际应用中,这一层可以不存在。例如,在教育团队早期的项目中,前端通过http方式调用后端服务。在这一层,我们通过提供REST服务与前端进行交互。之后,统一采用RPC调用方式,减弱了这一层的存在感。在这一层声明二方服务接口与前端node层交互,然后在应用层实现具体接口。

2.1.5 线索管理应用工程结构简单介绍

本小节将简要介绍线索管理项目涉及的应用工程目录结构,并对比四层架构。首先,来看工程目录结构,如图2-2所示。

出于商业保密性,实际工程结构中部分模块做了隐藏

图片

图 2-2 线索管理应用工程目录

目录中各模块的定义如下:

  • demo-api:接口层,负责系统间或对外的接口声明。通过RPC调用方式对外提供二方服务。

  • demo-biz:应用服务、领域服务处理层,接口层所声明接口的具体实现。

  • demo-dependency:外部系统的调用封装,比如,系统需要调用商品中心的服务,则需要在本 module 中封装 client。

  • demo-domain:领域层,系统领域的一些 model、上下文对象、仓储接口定义等。

  • demo-web:对外的 REST 接口。

  • demo-dal:基础设施层,数据持久化。

与 DDD 四层构架的对应关系见下表。

用户界面层demo-web
demo-api
应用层demo-biz(api)
领域层demo-biz(domain)
demo-domain
demo-dependency
基础设施层demo-dal

通过以上内容,我们可以了解到领域驱动设计的基本概念和分层架构。在实际项目中,我们可以根据需求复杂程度和业务特点,灵活运用四色原型图、用例图、时序图等工具,结合 DDD 思想,全面、准确地描述业务需求,构建符合业务需求的优秀领域模型。只有经过严谨的分析和设计,我们才能开发出高质量的软件系统,为教育领域的发展贡献力量。同时,在项目实施过程中,我们需要关注潜在学员信息管理的实时动态,以便为招生开发、教务调度等环节提供准确的数据支持,确保整个业务流程的顺畅进行。

2.2 需求分析利器 — 四色原型图

对于简单的需求,用例图往往足以阐述清楚。当需求变得复杂时,我们可以添加时序图、状态图等来进一步说明。然而,当业务流程异常复杂时,如何找出关键点以及各点之间的关联呢?是否存在一种科学的理论来指导我们进行分析呢?这时,我们可以考虑使用四色原型分析模式。它主要应用于业务分析阶段,有助于我们分析业务行为、参与对象以及业务对象之间的关系。

那什么是四色原型图呢?我们先来看下它的四个构成元素,具体如下:

(1)时刻-时间段原型(Moment-Interval Archetype)

原型简称 MI,表述的是某刻或某段时间内发生的一件事,比如:租房合同签署,是在某个时刻签署的,它有发生日期、行为人;租房行为是在一段时间内发生的,它有开始、结束时间和退租行为。这些我们都是可以通过此原型来表达的。在画原型图时,采用粉红色表示。

(2)参与方-地点-物品原型(Part-Place-Thing Archetype)

原型简称 PPT,用来表示参与某个活动的人或物,地点则是活动的发生地。比如签署租房合同这个行为,合同、承租人分别对应这里的物、人,中介办公室对应这里的地点。在画原型图时,使用绿色表示。

(3)描述原型(Description Archetype)

原型简称 DESC,是对 PPT 公共属性的描述,拿“签署租房合同”这个场景为例,在合同中会有一些租期、租金、押金、违约条件等约定,这些约定信息便可采用 DESC 原型来描述。绘制原型图时,采用蓝色表示。

(4)角色原型(Role Archetype)

原型简称 Role,这里的角色,指的是我们通常理解的“身份”。在签署租房合同场景中,行为人包括承租人和中介工作人员,这里的角色便是指“承租人”和“中介工作人员”。绘制原型图时,用黄色表示。

总结:如果必须要用一句话来概括四色原型的话,那就是:一个什么样的人或物以某种角色在某个时刻或某段时间内在某个地点参与某个活动。其中“什么样的”就是 DESC,“人或物”、“地点”就是 PPT,“角色”就是 Role,而”某个时刻或某段时间内的某个活动"就是 MI。

2.3 DDD 几个核心领域概念

2.3.1 实体

实体是一个具有身份和连贯性的概念,它具有以下几个特征:

  • 实体是数据(属性)和行为(业务逻辑关系)的结合体;

  • 每个实体都有自己的唯一标识,判断两个实体对象是否相等,是通过唯一标识来判断的。比如,两个实体对象,如果唯一标识相等,即使其他属性不相等,这两个实体也会认为是同一个。实体的其他属性不相等,表征的是同一个实体在其生命周期的不同阶段。

  • 实体的唯一标识属性值是不可变的,其他属性值是可变的。

举个例子简单说明下,比如在有赞精选内容平台(类似于小红书的电商导购平台)这个业务域中,每一篇“博文”就是一个业务实体,可以采用“博文 id”作为实体的唯一标识,然后这个博文实体拥有着属性(标题、作者、发表时间、内容等)和行为(更新博文、删除博文、关联导购商品等),同时,属性是会随着行为而不断变化的。

2.3.2 值对象

值对象一般会作为一个属性存放于一个实体内部,它具有以下几个特征:

  • 值对象不需要唯一标识,判断两个值对象是否相等,是通过值对象内部所有属性值是否相等来判断的。

  • 值对象的属性值不允许更改,即在创建后,其实体将保持不变。若要更改属性值,需先删除对象,然后重新创建一个新对象。

以“有赞精选内容平台”为例,用户可以在博文下留言,我们会挑选部分留言置顶。对于“置顶留言”,可以将其定义为值对象,并作为博文实体的属性。当置顶留言发生变化时,只需创建新的值对象,并将其赋值给博文实体的相应属性。

2.3.3 聚合

聚合是一组具有内在联系的领域对象(包括实体和值对象)的集合,其中一个或多个实体组成。每个聚合都有一个根实体(又称聚合根),主要负责与外部交互。外部对象若要访问聚合内的实体,必须先访问聚合根,再由聚合根与内部实体交互。

还是拿“有赞精选内容平台”举例说明,一篇博文中,它包含博文基础信息(内容、标题等)、关联的商品信息、关联的标签信息等,这一组合构成一个聚合,其中“博文基础信息”可作为聚合根。

2.3.4 仓储

首先说明,仓储被设计出来的初衷,在领域模型中,对象被创建出来后一般会在内存中活动,待其不活动了后,需要将其进行持久化存储。然后,当我们需要重建对象时,需要根据对象当前状态进行重建。可见这整个过程中,会频繁的与数据库(广义的数据库,包括关系型数据库、NoSql 数据库等)打交道,进行对象的创建、组装等。因而,能否提供一种机制,帮助我们管理领域对象以及做对象持久化,仓储并应运而生了。

仓储,又称资源库,它具有以下几个特征:

  • 仓储作为领域层与基础设施层的桥梁,将仓储接口定义放在领域层,具体实现放在基础设施层。这种解耦有助于减轻领域层与 ORM 之间的关联,任何 ORM 变更只需修改仓储实现,领域层接口定义无需修改。

  • 仓储存储的对象一定是聚合,因为领域模型以聚合划分业务边界。因此,我们只对聚合设计仓储。同样,在进行数据更新、删除等操作时,应以聚合为单位进行操作,而非仅操作聚合内的某一个实体。

三、线索资源管理 DDD 实战

结合四色原型图,设计领域模型的步骤可概括为以下几步:

  • 根据需求,采用四色原型分析法建立一个初步的领域模型;

  • 进一步分析领域模型,识别出哪些是实体,哪些是值对象,哪些是领域服务;

  • 对实体、值对象进行关联和聚合,提炼出聚合边界和聚合根;

  • 为聚合根设计仓储(通常情况下,一个聚合对应一个仓储),同时考虑实体、值对象的创建方式,是通过工厂创建还是直接使用构造函数;

  • 走查需求场景,验证设计的领域模型的合理性。

在图 1-1(线索管理业务总览图)中,我们可以看到“线索域”是核心部分。接下来,我将重点针对“线索域”,按照上述步骤一步步推导出其领域模型。

3.1 场景分析提炼四色原型图

无论是线上营销工具渠道还是线下地推渠道,线索收集环节最终触发的业务场景都是线索新增。而在线索管理环节,主要业务场景包括:新增/更新线索、查询线索、分配线索、跟踪线索和放弃线索等。这些业务场景在图 3-1 中可见。

图片

图 3-1 线索域业务场景用例图

(1)新增/更新线索四色原型图

根据业务规定,只有高级管理员、课程顾问、普通管理员等有权操作线索。依据四色原型的“一个什么样的人或物以某种角色在某个时刻或某段时间内在某个地点参与某个活动”的原则,我们可以得出操作过程中的参与方原型为商家,参与方角色包括商家高级管理员、课程顾问、普通管理员等;物品原型为线索,包括线索基础信息、线索标签、线索来源;参与的活动为“新增/更新线索”。提炼出的四色原型图见图 3-2。

图片

图 3-2 新增/更新线索四色原型图

(2)查询线索

参与方原型为商家,参与方角色包括商家高级管理员、课程顾问、普通管理员等;物品原型为线索基础信息、线索标签、线索来源;参与的活动为“查询线索”。提炼出的四色原型图见图 3-3。

图片

图 3-3 查询线索四色原型图

(3)分配线索

每个线索若需分配跟进人,必须指定一个跟进者。如果当前跟进人无法完成线索跟踪,可以将线索转让给其他人(线索分配者、线索承接人、线索原跟进人的身份均为“高级管理员、课程顾问、普通管理员”之一)。根据上述场景,参与方原型为商家,参与方角色包括商家高级管理员、课程顾问、普通管理员等;物品原型为线索基础信息、线索标签、线索来源;参与的活动为“分配线索”。提炼出的四色原型图见图 3-4。

图片

图 3-4 分配线索四色原型图

(4)跟踪线索

课程顾问在获得分配的线索后,需要进行线索跟踪。在跟踪过程中,课程顾问可以记录相关的跟踪信息。此时,参与方原型为商家,参与方角色是课程顾问;物品原型为跟踪记录;参与的活动为“添加跟踪记录”。归纳出的四色原型见图 3-5。

图片

图 3-5 线索添加跟进记录四色原型图

(5)放弃线索

如果课程顾问认为当前线索难以跟进,可以选择放弃该线索。从这一场景中,我们可以看出:参与方原型是商家,参与方角色是课程顾问;物品原型是线索(包括基础信息、标签、来源);参与的活动是“放弃线索”。提炼出的四色原型图见图 3-6。

图片

图 3-6 放弃线索四色原型图

综合以上所有场景,可得出图 3-7 所示的“线索域”四色原型图。

图片

图 3-7 线索域四色原型图

3.2 领域模型中实体/值对象/领域服务/聚合识别

通常,我们可以将四色原型图中的原型与 DDD 进行简单映射。例如:PPT 原型表示活动中的唯一个体,可对应 DDD 中的实体;Role 原型描述实体在不同状态下的表现,通常将其放入实体中,共同构成带状态的完整实体;DESC 原型表示 PPT 的公共属性,一般作为值对象存储;MI 原型描述特定活动,可间接对应领域服务。

我们回过来看下图 3-7,在图中,有“商家、线索基础信息、跟踪记录、来源信息、标签信息”5 个 PPT,我们可以据此定义 5 个实体,“高级管理员”、“课程顾问”、“普通管理员”可以认为是商家在不同身份下的表现,可在商家对象中使用一个标识符来描述。于是,我们可以总结出以下实体,见图 3-8。

实际的线索信息比图 3-8 中定义的要复杂,出于商业保密性,这里仅列出部分字段,且部分字段采用 xxx 来表示。

图片

图 3-8 线索域实体

接着,我们进一步分析实体间的关系,提炼出聚合边界和聚合根,并定义出仓储。

在线索域中,线索是核心,很明显 ClueEntity 与 SourceEntity、RecordEntity、TagEntity、UserEntity 是相关联的,而后四者间是没有联系的。首先,来看下 ClueEntity 和 UserEntity,线索在创建之初是可以没有跟进人(用户)的,但在之后被跟进的过程中,需要强制绑定一个跟进人(用户),而用户脱离线索是不具有存在价值的。同时,本项目中,用户信息仅作为线索的归属属性存在,最终我们将 UserEntity(改名为 UserVO)作为值对象放置于 ClueEntity 内,且令线索信息实体为聚合根;然后,分析下 ClueEntity 和 SourceEntity、TagEntity、RecordEntity,主要从两个方面考虑是否需要组成聚合:

(1)聚合应具备内部一致性,即聚合内对象要么一起获取,要么一起更新,要么一起删除。若聚合内任意对象在保存时被修改,都应视为聚合被修改,此时保存失败。因此,在定义聚合时,保证合理性的前提下,尽量设计较小聚合。在线索管理中,线索管理人员频繁为线索关联标签、来源、跟进记录等信息,从内部一致性角度考虑,三者分开更好。

(2)聚合内聚合根和对象间要保持不变性。何为不变性?简单来说,对象之间存在某种不变的规则。举个例子说明下,x=y+5,如果规定 y 大于 1,那么 x 一定大于 6。回到线索管理,ClueEntity 和 SourceEntity、TagEntity、RecordEntity 间并不存在这种不变性,因为任意一个来源、标签、跟踪记录一定有一条对应的线索,但一条线索可以没有来源、标签、动态记录,同时,来源、标签、跟踪记录均可以在各自领域被单独访问到。

综合以上两点,我们采取如下策略:SourceEntity、TagEntity、RecordEntity 各自定义为一个聚合,本身作为聚合根。然而,在查询线索详情时,线索包含来源、标签、跟踪记录信息,而 ClueEntity 聚合内不包含这些信息,如何实现信息聚合?我们采用领域服务来实现领域对象间的聚合。

最后,定义仓储。我们遵循一个聚合对应一个仓储的原则来定义。最终,我们得到了如下表所示的领域模型。

仓储 聚合 聚合根
ClueRepository ClueAggregate ClueEntity, UserVO
SourceRepository SourceAggregate SourceEntity
TagRepository TagAggregate TagEntity
RecordRepository RecordAggregate RecordEntity

对应的类图可见图 3-9。

图片

图 3-9 线索域类图结构

通过以上步骤,我们可以构建并完善线索管理的领域模型。在实际项目中,根据需求复杂程度,我们可以灵活运用四色原型图,并与用例图、时序图、状态图等相结合,全面准确地描述业务需求。同时,不断审视和调整领域模型,以确保其合理性和有效性。只有经过严谨的分析和设计,我们才能构建出一个符合业务需求的优秀领域模型。

四、总结与思考

本文以“线索资源管理”实际项目为背景,详细阐述了从需求分析到方案设计阶段,如何运用 DDD 理念逐步构建领域模型。首先,在第一章中,我们重点介绍了项目背景及其在教育领域的重要性,并给出了主要业务场景,以便读者对项目有一个全面了解。接下来,在第二章中,我们详细介绍了 DDD 的分层结构、需求阶段可用的四色原型分析法以及方案设计阶段所需的几个 DDD 领域概念。最后,在第三章中,结合前两章的项目背景和领域概念,我们一步一步地构建了本次项目的领域模型。

在实际项目中,我们可以根据需求复杂程度和项目特点,灵活运用四色原型图、用例图、时序图等工具,结合 DDD 思想,全面、准确地描述业务需求,构建出符合业务需求的优秀领域模型。只有经过严谨的分析和设计,我们才能打造出高质量的软件系统,为教育领域的发展贡献力量。

说在最后

DDD架构如何落地,是非常常见的面试题。

以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,并且在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

当然,关于DDD,尼恩即将给大家发布一波视频  《第34章:DDD的学习圣经》, 帮助大家彻底穿透DDD。

图片

部分历史案例