DDD笔记1:简介及战略设计
1.DDD的背景和来源
1.1 DDD的核心思想和解决的痛点问题
1.1.1 什么是模型和建模
模型是对领域的抽象和模拟。
建模是针对特定问题建立领域的合理模型。
1.1.2 软件系统的复杂性来自哪里
复杂性来源:
- 业务复杂导致模型复杂
- 技术实现引入额外复杂性
业务复杂性导致模型复杂
技术实现引入额外复杂性
可以引入搜索防腐层:
1.1.3 DDD的关键思想以及如何应对多变的复杂性
核心思想一、模型分解
- 领域划分是面向问题空间的
- 限界上下文是面向解决方案空间的
核心思想二、模型驱动设计(Model Driven Design)
- 通过分层架构隔离领域层、仔细选择模型和设计方案等措施保持实现与模型的一致。
1.2 面向对象和敏捷与DDD的区别与联系
面向对象分析与设计没法应对大型复杂系统。
敏捷开发流程:
1.3 DDD的过去、现在与未来
应用
- 产业互联网
- 企业数字化转型
理论和工具
- 更多建模方法和工具
- 与大数据和AI的结合
2.战略设计
2.1 案例背景介绍
面向自动售卖机的零售SaaS
团队背景
- A公司:SaaS公司,主要面向零售企业客户
- SmartRM产品团队:一位资深产品经理,几位产品策划
- SmartRM研发团队:一位架构师,10+开发,资深开发3至4位
客户背景
- B集团,零售行业某头部商家
- 全国各地有大量商超和便利店,有成熟的内部系统和供应链
- 基于自动售卖机的零售业务是新业务
2.2 建模和设计的整体流程
整体流程(重叠的,建模涡流):
- 挖掘用户故事
- 建立通用语言
- 战略设计
- 战术设计
挖掘用户故事
- 问题空间的描述
- 文字表述
- 讨论 + 图形表达
建立通用语言
- 在讨论模型和定义模型时,团队使用的同一种语言
- 领域知识需要在团队内部高效流转,模型需要描述
- 通用语言要体现在代码里
战略设计
- DDD中对问题空间和解决方案空间进行分解的过程
- 目的是分解模型以控制复杂性
- 是DDD与传统建模和设计方法的核心区别之一
战略设计主要做三件事:
- 领域划分
- 寻找限界上下文(BC)
- 确定上下文映射
战术设计
- 对各个BC的细节设计过程
- BC内部的模型结构与完整技术方案
常用的建模方法
- Domain Storytelling 领域故事陈述法
- Event Storming 事件风暴法
- 4C 四色建模法
2.3 如何描述模型?从用户故事到通用语言
2.3.1 用户故事
用户故事
- 不要拍脑袋定需求
- 基于用户故事讨论
什么是用户故事?
- 在软件开发中,用户故事是一种对软件系统特性的非正式的自然语言描述,是敏捷软件开发中从终端用户的角度对软件系统特性进行捕捉的一种方式。用户故事描述了不同类型的用户需要什么以及为什么需要,它可以帮助我们创建需求的简单描述。
- 在软件开发和演进过程中,随着产品和开发对产品认识的加深,需求总是在不断变化,所以,过早地进入需求细节以及对细节的描述,是一种时间上的巨大浪费。从这一点来说,用户故事提供了一种恰到好处的粒度,使得产品在需求分析阶段能够极大地节约时间,并且使产品和研发人员始终把注意力集中在关键点,避免他们过早地陷入细节以及被细节所局限,同时给产品功能留出了讨论空间,从而使产品有机会在讨论过程中得到优化。
用户故事的构建一般来说有三个环节:
- 1)简单描述用户需求;
- 2)围绕简单描述进行讨论;
- 3)明确如何验证。
分别对应用户故事的三个元素,也就是3C:Card(卡片)、Conversation(谈话)、Confirmation(验证)。
- Card卡片:指对用户故事的简述(传统上人们通过便利贴在白板上构建用户故事),一个好的用户故事卡片包括三个要素(谁Who:谁需要这个功能;需要什么What:想通过系统完成什么事情;为什么Why:为什么需要这个功能,这个功能带来什么样的价值)(As a role, I want to action, so that benefit)
- Conversation:谈话是指用户、领域专家、产品经理、研发之间围绕用户故事进行的讨论,谈话是明确需求细节的必要环节。可以用文字对谈话进行简要记录,此外,也可以基于图形或其他工具进行讨论。
- Confirmation:验证代表了验收测试,描述了客户或者产品owner怎样确定用户故事已经被实现,且能够满足需求。
Confirmation模板:
假设我是<角色>,在xxx情况下,
当我<操作>,
那么<结果>。
2.3.2 使用用户故事收集和梳理SmartRM的需求
- 售卖机扫码支付购物 售
- 卡片
作为用户,
我希望在售卖机上通过手机扫码支付购买商品,
以便快速便捷地购物。 - 谈话
- 验收标准
假设我是一名用户,货道售卖机屏幕的商品列表上有商品A, B, C,
当我在售卖机屏幕上选择了商品A,并扫描展示的二维码完成支付后,
那么商品A就会从售卖机中弹出,我可以拿到商品A。
- 柜门机免密购物
- 卡片
作为用户,
我希望在支持自动结算免密支付的柜门机上扫码开门后拿到商品,关门就可以结束购物,系统后台可以自动结算并且扣费,以便在柜门机上能更快捷地进行购物。 - 谈话
- 验收标准
假设我是一名用户,柜门机里有商品A, B, C
当我打开柜门,从柜门机拿走商品A并且关闭柜门,
那么后台会自动完成结算和支付,从用户账户扣除商品A的价格,并且锁定柜门。
其他顶层用户故事还包括:
- 售卖机投放
- 售卖机撤销
- 补货补
- 经营分析
2.3.3 使用Domain Storytelling分析用户故事
Domain Storytelling是一种领域分析建模方法,通过这种方法,产品和研发人员可以利用语言学习的相同原理建立或者学习一个领域的通用语言,并且建立领域模型。
在storytelling过程中,一方(通常是架构师)聆听另一方(通常是产品经理或者领域专家)以主谓宾的句型(谁做了什么)讲述用户故事的流程,进行问答和讨论,并以图形的方式快速复述出来。
使用在线工具domain-story-modeler(https://github.com/WPS/domain-story-modeler),可以在线实践domain storytelling。
可以更进一步,将通用语言中的词汇提炼出来,将其中英文都列在通用语言词汇表中,这些词汇将会贯穿整个建模和设计过程,最终也会体现在代码中,因此团队中所有成员,都需要明确理解其含义,并且在相关讨论、模型、以及代码中使用它们。
通用语言
- 一种描述模型且基于模型的语言
- 团队在进行所有交流时都使用它
- 代码中也要体现
通用语言
- 类和操作的名称
- 施加于模型之上的规则和约束
- 应用于领域模型的模式
2.4 分解问题:领域划分和子域
2.4.1 什么是领域划分和子领域
领域划分是以分离关注点为原则对问题空间的划分。基于领域划分进行分工协作而非基于需求。
子域是领域中某个方面的问题和解决它所涉及的一切。
2.4.2 为什么要进行领域划分
传统模式的问题(6个需求分配给6个员工开发)
- 问题点和领域知识重叠
- 模型重叠
领域划分可以解决传统模式的问题
- 不同子领域聚焦解决不同问题
2.4.3 基于用户故事分解的领域划分方法
最终完整的划分
- 交易域
负责面向终端用户的商品交易,用户购买商品时使用的设备前端界面、完成后台交易的程序都在这里。 - 支付域
- 商品域
- 用户域
- 运营域
- 设备域
实现设备机售卖的管理和操作。
2.5 确定系统最核心的部分:核心域和精炼
战略设计要明确核心域,团队尽量减少非核心域投入。
2.5.1 子域的类型
子域
- 核心域(公司利益所在,相对稳定)
- 通用域
- 支撑子域
2.5.2 核心域的意义
公司利益所在,相对稳定。
2.5.3 什么是精炼以及精炼的方法
精炼
- 分离出领域中普通的部分,从而得到领域中最精华的部分
精炼的方法
- 萃取
逐渐体现出关键作用的核心部分 - 分离
过滤掉不重要的部分
SmartRM核心域
- 交易域
- 运营域
2.6 分解模型:限界上下文
限界上下文是在解决方案空间对模型的分解单位。
2.6.1 什么是限界上下文
限界上下文是一种语义上的上下文边界。意思是在这个边界里的软件模型组件都有它特定的含义并且做特定的事。一个限界上下文内的组件都是上下文特定的并且语义明确的。
2.6.2 为什么需要限界上下文
原因:
- 自然语言具有模糊性
- 同一个事物面向不同场景有不同模型
- 软件系统需要分解模型以控制复杂性
- 限界上下文是分工的单位
理想情况,子域和限界上下文是一一对应的。
2.6.3 如何划分限界上下文
三种方法
- Domain Storytelling 领域故事陈述法(建立通用语言、领域划分,也可以深入解决方案空间建立模型)
- Event Storming 事件风暴法
- 基于子域概念提取
Domain Storytelling中边界的特征
- 单向联系
- 语义区别
- 活动的触发方式不一样
基于子域概念提取
2.6.4 限界上下文和微服务的关系
微服务是限界上下文的实现方式。
2.7 多个上下文之间如何协作?上下文映射和防腐层
2.7.1 什么是限界上下文?为什么需要?
Context Mapping。
上下文映射是指限界上下文之间的模型映射关系。
描述团队之间的协作关系以及上下文之间的集成关系。
决定上下文之间如何集成一级如何设置防腐层。
2.7.2 上下文映射有哪些模式?
九种模式
- Open Host Service (中台、第三方公共服务:微信支付上下文)
服务提供方为所有消费者提供一套公共的API。
针对通用的功能和模型。 - Conformist
没有模型到模型的转换。
一个上下文沿用另一个上下文的部分模型。 - Big Ball Of Mud(反模式,应该避免)
由混杂的模型构成的糟糕系统,模型不稳定且难以维护。
与大泥球合作的上下文要确保自身不被污染,设置防腐层。 - Anticorruption Layer
把上游上下文的模型转换成自己上下文的模型。
是上游上下文中访问外部模型的一个代理层。 - Shared Kernel
两个上下文共享部分模型。
包括但不限于代码、jar包、.so、数据库表等。
慎用,仅当团队紧密合作且共享部分稳定。 - Partenership
技术无关,是一种团队协作关系。
两个团队之间可以随时互通有无,协同变更。 - Customer/Supplier
下游上下文可以向上游上下文提需求。
一般用于核心域与非核心域之间的协作。 - Separate Ways
两个上下文无协作,各自独立。
当两个上下文之间的集成成本过高。 - Published Language
标准化与协议化的模型。
所有上下文都可以与公开语言中的模型进行转换。
对接了公开语言的上下文之间可以实现组件化对接。
2.8 剥离领域模型与技术实现:建立分层架构
2.8.1 为什么要隔离领域模型
原则:严格按照领域模型来编写代码
但是建模和实现中都有破坏该原则的因素。比如为了性能,创建不存在的实体。
架构分层能够避免模型在实现过程中被省略或者污染。
贫血模型:
- 围绕数据(数据库表)进行操作
传统方式的问题
- 领域模型容易被省略,变成贫血模型
- 容易演变成基于数据的设计,一切从表结构开始
- 领域模型与技术实现混杂,易被技术实现绑架
2.8.2 DDD传统四层架构和洋葱(六边形)架构
四层:
- 接口层:协议解析、封装、消息路由
- 应用层:面向问题空间
- 领域层
- 基础设施层:通用的框架和工具
四层优点:
- 分离关注点
- 让领域模型层更独立
- 单向依赖
四层缺点:
- 领域层对基础设施层仍然有感知,领域模型和技术实现有耦合
解决方法
- 依赖倒置,将操作下放到基础设置层,然后领域层装配基础设施层即可
六边形架构优势
- 保持领域层的纯粹性,不受其他因素干扰
- 便于践行模型驱动设计,代码跟随模型
- 便于把团队精力集中到领域模型