DDD实战之三:整体工作框架和全局需求分析(上)

简介: DDD实战之三:整体工作框架和全局需求分析(上)

从今天开始,我就要进入“群买菜”系统真正的 DDD 设计和实现过程了。这里面会涉及到一些 DDD 相关的概念和理论,但更多的是设计和代码实践。


正如第一篇“缘起”中提到的,本专题将尽可能的将理论知识进行浓缩和简化、糅合到实际的设计文档和代码实现中去表达。


DDD 本质上是个“软件设计”方法论,它指导的是“怎么做软件设计”。故在正式开始“群买菜”的软件设计之前,我们先对 DDD 整体方法论(这里主要是张逸老师的 DDDUP)做一个简单的、从我个人角度理解的介绍。


本篇在对 DDD 整体工作框架做了个简要的介绍后,我会在本篇中完成 DDD 工作框架中的第一步——“群买菜”系统的全局分析。


01DDD整体工作框架


在我个人的理解中,DDD 工作框架其实就只有两个部分:对“问题空间”的分析和描述、以及通过一系列方法实现的“解空间”的映射。“问题空间”其实就是在目标软件系统上线前、当下的现实业务世界,“解空间”就是目标软件系统的设计方案和代码实现。


1


“问题空间”分析:全局分析


对“问题空间”进行分析和描述,就是“问题空间”的所有工作内容。它指的是要搞明白我们的软件“到底要解决什么问题、以及满足什么业务需求”——说得更直白一点,就是传统上大家理解的“业务需求分析”。不过,我们在 DDDUP 方法体系里,不叫“需求分析”而叫“全局分析”。


首先,我们需要了解 DDD 中反复提到的“业务领域”和“业务子领域”的概念,并将“业务子域”区分出 3 个类别:核心子域、通用子域、支撑子域。


所谓的“业务领域”,指的就是“业务领域模型”。因为 DDD 是“领域驱动设计”,所以“业务领域模型”是 DDD 的核心概念来源。而”业务领域“就是所开发软件系统需要对应的现实世界的某个“业务范畴”,比如:电商、物流、通信、交通、医疗、教育等等。


在“业务领域”下,我们可以将其业务内容分为多个“子领域”,比如:“电商”业务领域下,有“客户”、“商品”、“库存”等等多个子领域。


需要注意的是:“业务子领域”的划分其实是不考虑将来软件系统会怎么建设的,而更多是考虑现实业务中需要怎么区分这些业务单元。当然,有可能系统实际实现后,系统的模块也会有跟“业务子领域”一样的划分——但这只是“系统同构化映射现实业务”,并不是系统模块划分在影响业务单元划分。也就是说:“业务子领域”划分是“因”、“软件模块”划分是果。甚至有可能根据实际项目的现实条件,软件系统并没有按照“业务子领域”进行分块。比如:重用一个既有的整体的 SAP ERP 系统来实现“采购”、“库存”、“财务”多个“业务子域”的支撑。


“业务子领域”按照如下的识别规则划分为 3 类:核心子域、通用子域、支撑子域。


“通用子域”就是那些无论在哪个行业、哪个业务领域下都可以通用的“业务子域”,比如:角色权限、组织管理、员工、IM 即时消息等等。这些“通用子域”所对应的软件实现,很多时候是可以直接在市面上采购成熟产品来实现的,不见得非要本项目的软件团队来实现。


“支撑子域”是那些虽然有本行业、本业务领域下特定的业务知识,但并不是本业务领域下最核心的业务单元。比如:“电商”业务领域下的“库存”、“物流”、“财务”就不是核心业务单元,而只是支撑“订单”、“客户”等核心业务单元的、后台人员所使用的与直接客户无关的业务单元。


“核心子域”就是那些体现本“业务领域”最核心业务价值的业务单元。当然,这其实是个“相对概念”,是在特定语境下才有效的说法。比如:在“电商”业务领域中,“物流”就是个“支撑子域”;但在“快递”业务领域中,“物流”就是个“核心子域”。


其次,我们大致了解下 DDDUP 对“全局分析”要求的工作内容(这里您不必全部记住,只是大致浏览即可。我在本节的“群买菜”全局分析中会给您演示怎么做):


价值需求分析——对目标软件系统的价值需求进行调研总结;


核心业务流程分析——对目标软件系统将要覆盖的、体现最为“核心商业价值”的业务主流程进行调研分析,画出流程图;


业务场景识别——识别出按照时间轴的跨度,业务主流程有哪些在时间线上“断裂”的业务场景;


业务流程分析——对每个业务场景下的业务流程进行识别,并用跨角色的泳道图、业务服务蓝图、事件风暴图等工具(如果您不明白这些工具是啥,暂时不管它)表达出各场景下的、各情况的业务流程;


业务用例识别——在泳道图、业务服务蓝图、事件风暴图等工具表达的业务流程基础上,识别出系统将要满足的“业务用例”。没错,就是 UML 的那个“业务用例”;


编制业务用例规格书——就是每个业务用例给一个需求规格书(按照规定的模板格式)描述文档;


2


“解空间”映射:战略设计+战术设计+代码实现


首先,我们同样需要了解 DDD 中提到的关于设计的最重要几个概念:


战略设计层面的 2 个重要概念:限界上下文、上下文映射。


限界上下文就是目标系统内部最粗粒度的模块划分,这个粒度的模块划分往往是后续“微服务”怎么切分的主要依据。


这种限界上下文的划分,最理想的情况就是一个“业务子域”对应一个“限界上下文”(所谓“同构映射”)。但在实际软件系统设计中,可能因为“第三方伴生系统”、“遗留系统”等情况的存在,而不得不出现“错配”的情况,就如下图所示——也就是说:只有所有“业务子域”对应的业务逻辑都全部由目标软件系统“全新实现”才可能出现“最理想情况”。


image.png


限界上下文映射就是搞清楚这些“限界上下文”模块之间是怎样的协作关系(调用关系、事件通知关系等等)。


“限界上下文”的划分,最重要的就是追求的“高内聚、低耦合”。而这其实是个“无止境”的追求,并且不同的架构设计师、其经验和对业务的理解不同,自然设计成果也不会完全相同,所以严格来说这是一种“很艺术化”的设计成果。


但无论怎样,DDD 战略设计中有一系列的方法和原则来帮助达到您尽可能地达到“高内聚、低耦合”的设计效果,我们会在后面的实例演示中逐步看到。


战术设计层面的 4 个重要概念:实体对象、值对象、聚合、领域服务。


实体对象是对象模型中最主要的类,需要数据生命周期管理的、根据 ID 标识而不是属性来判断是否同一个对象的类。如:订单、订单行等。


值对象是不需要数据生命周期管理的(它们往往用来描述实体对象的属性)、只要其对象内的属性取值发生变化该对象就被认为是另一个对象。如:地理区域(含省市区)、家庭住址(含经纬度定位和详细地址)、身份证号(含编码规则)、姓名(含姓+名)等。


聚合和聚合根。在实体对象中,有些实体对象是不需要单独出现的、总是跟着另一个实体对象的出现而出现、消亡而消亡的,如:”订单行”总是随着“订单”的出现而出现、消亡而消亡。往往,我们在完成“对象模型和关系识别”后,列出了很多实体对象,这些对象可按照它们之间的“绑定存亡关系”进行分组,分组后有一个实体对象是唯一的访问入口。这种分组就叫“聚合”,而那个作为唯一的访问入口的实体对象就是“聚合根”——一般情况下,给“聚合”的命名就是“聚合根”对象的名称,如:“订单”聚合可能就包含了“订单”、“子订单”、“订单行”、“商品快照”等多个对象(后面的对象都不需要单独出现)。需要说明的是:值对象是“附庸”在实体对象上出现的。


领域服务。实体对象、值对象都是有行为的(也就是方法逻辑),很多业务逻辑就直接在这两类对象中实现了。那么,有了“聚合”就可以将很多业务逻辑在“聚合”内部的各个实体对象、以及伴随的值对象的方法逻辑中实现。但是,仍然有一些不能明确划分到单个聚合”职责“中去实现的业务逻辑,而这些逻辑就需要在“领域服务”中去实现。比如:“为当前登录用户创建订单”这一业务逻辑里面,既包含要根据当前登录用户的 ID 找到对应的客户资料,还包含根据请求输入信息创建订单记录。那这些逻辑,到底应该是由“客户”聚合中实现呢?还是在“订单”聚合中实现?显然,无论放在“客户”还是“订单”聚合中,都是不合适的,所以就有了“为当前登录用户创建订单”这一领域服务存在的必要性。


其次,我们在了解 DDD 战略、战术设计的几个重要概念后,就可以来看看战略设计、战术设计分别包含什么工作内容:


战略设计主要完成如下 3 方面工作:


系统上下文定义——将目标系统与伴生系统进行职责分工。


限界上下文识别及其对应的上下文映射。


战略层面的技术决策——主要包括:

  • “系统整体”角度的软件分层(区分边缘层、业务价值层、基础层);
  • 系统整体角度的架构性决策:微服务的划分、事务一致性策略(全局一致性 vs 最终一致性)、数据库架构(单体数据库 vs 分布式数据库);
  • 必要的各限界上下文的技术栈选择(开发语言、技术组件、使用规则引擎考量、使用 CQRS/命令总线考量、事件消息机制等);


战术设计主要完成 4 方面工作:


对象模型的概念分析——识别出有多少实体对象、值对象。


聚合设计——将实体对象进行聚合分组,选出每个聚合的“聚合根”。


服务设计——设计出“领域服务”,并在“领域服务”的基础上设计“应用服务”。这两者的区别,以及如何设计在后面的实例中会介绍。


战术层面的技术决策——包括但不限于:

  • 如何在前端界面和后端服务之间传输数据(VO:view object)
  • 微服务远程服务调用时如何传输数据(DTO:data transfer object)
  • 使用开发语言的哪个持久化框架实现数据持久保存(如 JPA/Mybatis 等)
  • 如何实现资源库端口等。


好了,我们已经对 DDD 整体的工作框架做了一个特别简短的介绍,下面完成本篇文章的正题——群买菜全局分析。


02群买菜系统全局分析


1业务背景和价值需求分析


说实在的,我写这款“群买菜”小程序,是发现很多生鲜店老板或团长在使用“群接龙”这款小程序。而实际上“群接龙”并不能很好的满足生鲜行业的一些特定行业特性——如:活鲜类商品的多退少补等。


所以,“群买菜”其实是一款给生鲜社区店或社区团长提供的,允许消费者在线下单、接龙以及店主或团长进行后台店铺、商品、订单等管理的小程序。描述一个软件产品商业价值的最好方式是“商业模式画布”,下图就是“群买菜”小程序的商业模式画布:


image.png


通过该“商业模式画布”,我们能够看出我们开发“群买菜”小程序,其主要的用户就两类:消费者(生鲜客户)、商家(生鲜店老板、或社区团长),主要商业价值是通过满足“消费者”和“商家”之间的“在线交易”来实现的。所以,“群买菜”系统涉及到的利益干系人(stake holder)包括 3 个:

  • 消费者(生鲜客户)
  • 商家(生鲜店老板、或社区团长)
  • 平台运营方(群买菜软件的提供方、运营方)


基于这 3 个利益干系人,我们分析各方的“利益诉求”,得出如下的“价值需求规格书”:


image.png


2产品需求输入和统一语言


首先,我们来看看“群买菜”系统的需求输入——产品原型设计。


在 DDD 社群中,大家争论和激烈的一个话题就是:“产品经理”在 DDD 设计中到底起到什么作用,还需要“产品经理”这个角色存在吗?


这里我分享下我个人的观点:DDD 作为软件设计方法,解决的是“工程性问题”;而产品经理作为产品直接负责人,是要从商业利益、用户心理、公司资源等多个方面进行综合考虑的,他在产品设计中所完成的“原型设计”作为产品设计中最重要的成果之一,是在将关于“非工程性问题”的思考结果表达出来。


所以,我在这里秉承的观点是:产品经理输出的产品设计,作为 DDD 全局分析的输入,而不是用 DDD 全局分析代替产品经理的工作。否则,这在公司“商业职责”的定位上就很不清晰了。


于是,我这里“群买菜”系统的“全局分析”是在“群买菜”产品原型设计的基础上进行的。“群买菜”产品原型的部分设计截图如下:


image.png


由于产品原型界面很多,这里就不一一介绍了,有兴趣的您可以到这里去在线浏览:《群买菜产品原型设计》(https://xie.infoq.cn/link?target=https%3A%2F%2Fwww.stardata.top%2Fstarshop%2520mini-app%2Findex.html

接下来,在开始后面的分析设计之前,我们需要完成 DDD 设计方法论中特别重要的一步工作:制定统一语言(UL)。我个人的理解,所谓“统一语言”其实就是一个“术语表”。在大型项目中这样的“术语表”特别重要,一种比较务实的做法是:不要为所有“业务子域”制定一个“大而全”的术语表,而制定“分业务子域”的术语表(即统一语言)。鉴于“群买菜”并不是一个“真正的大工程”,我这里只为“群买菜”系统给出一个集中的术语表。同样限于文章篇幅,我截图部分术语表如下图:


image.png


如果您对完整的“群买菜统一语言”(即术语表)感兴趣,可访问在线页面《“群买菜”统一语言》https://xie.infoq.cn/link?target=https%3A%2F%2Fwww.stardata.top%2Fqunmaicai-ul.html)。


3核心业务流程和业务场景


有了价值需求分析、产品需求输入、以及“统一语言”定义,我们就可以开始基于业务的核心流程(也就是软件要实现的“最核心价值流程”)对业务场景进行划分、并对每个场景下的业务流程进行细化设计,进而完成两个最重要的工作:业务用例识别(也叫“业务服务”,是后续”应用服务“、“领域服务”设计的最主要基础)、业务子域识别与分类。


根据“群买菜”的价值需求分析,我们知道“群买菜”最核心的业务目的就是:满足“社区生鲜”这一“业务领域”下,“客户”和“商家”的在线交易和接龙活动。为此,我们设计出”群买菜“的核心业务流程如下图:


image.png


在进行后续的业务流程具体分析之前,我们需要将“问题空间”进行“简化”,也就是常用的“化整为零”。一般来说,对业务流程来说“化整为零”的第一步是进行“业务场景切分”——也就是按照时间轴、把时间上可以“断裂”开来的流程进行切分。就我们“群买菜”系统来说,从“时间轴”上可以“断裂”的业务场景分析如下:


商家建立店铺。商家在线销售商品之前,需要先在系统中建立店铺,并设置店铺的相关营业属性(营业时间、是否送货上门等)。这个操作,对于商家来说,可能只需要几分钟的时间。


商家建立和维护商品库。商家建立店铺后,系统并不会要求立即建立商品库,所以建立商品库和店铺是可以在“时间上断裂”的。


客户在线浏览和下单。一般来说,客户只有在商家商品库后,才有可能在店铺浏览商品和下单。但显然,并没有一个业务规则要求“客户浏览和下单”必须和“商家建立店铺、商家建立商品库”在时间上是连续操作的。事实上,商家可能只需要建立一次店铺、建立一次商品库,但客户可以发生无数次在线浏览和下单。


在线接龙活动。按照产品需求设计,系统还需要支持商家发起的在线接龙活动。在线接龙和在线销售的区别是:在线销售是一次建立商品库、但发生无数次销售;而接龙活动是在商家每次建立后、客户只能参加一次接龙。所以,商家建立接龙活动、到客户参加接龙活动在“时间轴”上是连续的。


商家结算营业收入。商家可以定期进行营业收入结算,只要客户在系统上确认了收货(或者系统超时自动收货),则商家就可以将资金提现到自己的账户下。这其中,可能涉及到系统平台运营方与商家之间的手续费结算。这一营业收入结算过程,从时间轴上来说,也是一个独立的业务场景。


商家加盟品牌。在产品原型设计中,我们可以看出产品经理还希望系统能够支持商家店铺之间的“轻加盟”模式,以实现店铺的“轻连锁”或实际的“加盟分销”。这种“轻加盟”模式,就是建立店铺和店铺之间的“品牌关联”关系,使得加盟某店铺的新店铺,可以销售被加盟店铺(称为“品牌店铺”)的所有已上架商品。从时间轴角度来说,显然作为品牌店铺设置加盟政策、以及选择哪些品牌店铺进行加盟,这是另一个”时间断裂“的独立业务场景。


根据如上的分析,再结合上述 6 个业务场景之间的逻辑关系,我们画出如下的主体业务流程图:


image.png


4各场景下的业务流程(泳道图)


上面的 6 个业务场景,我们是可以针对每个场景给出业务流程的。分别设计如下:


建立店铺。业务流程如下图:


image.png


加盟品牌。业务流程如下图:


image.png


相关文章
|
前端开发 测试技术 数据库
DDD架构中assembler和converter的区别
在 DDD 四层架构模式中,assembler 和 converter 常用于对象转换,但两者在实际项目中的使用较为随意。本文从英文释义、语义区分和模型层区分三个方面探讨了两者的区别,建议按模型层区分,即 Interface 和 Application 层使用 assembler,Infrastructure 层使用 converter,以避免混淆和随意使用。此外,将转换代码抽离为独立方法有助于保持代码整洁和可测试性。
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
9288 116
|
机器学习/深度学习 算法 开发工具
【YOLOv8量化】普通CPU上加速推理可达100+FPS
【YOLOv8量化】普通CPU上加速推理可达100+FPS
2310 0
|
设计模式 缓存 Devops
微服务架构最强讲解,那叫一个通俗易懂!
微服务架构(Microservice Architecture)是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。你可以将其看作是在架构层次而非获取服务的
33469 3
微服务架构最强讲解,那叫一个通俗易懂!
|
设计模式 供应链 数据可视化
DDD - 事件风暴从理论到落地
DDD - 事件风暴从理论到落地
908 1
|
消息中间件 测试技术 领域建模
DDD - 一文读懂DDD领域驱动设计
DDD - 一文读懂DDD领域驱动设计
47826 6
|
JavaScript 前端开发 IDE
程序员必知:WPSJSA宏编程(JS):1.初识
程序员必知:WPSJSA宏编程(JS):1.初识
1896 0
|
设计模式 Cloud Native 前端开发
DDD 实战之一:从需求到代码实现生鲜电商系统
DDD 实战之一:从需求到代码实现生鲜电商系统
|
前端开发 JavaScript NoSQL
DDD实战之二:看看代码结构长啥样
DDD实战之二:看看代码结构长啥样
DDD实战之二:看看代码结构长啥样