DDD领域驱动模型设计与微服务架构
战略设计:从业务角度出发,建立业务模型,划分业务边界
领域
一个领域本质上就是一个问题域,只要是同一个领域,那问题域就相同
相同领域,他的领域模型并不能完全复制
陶宝 C2C 核心是交易保证,同时兼顾产品质量
京东 B2C 口碑 核心是产品质量 采购 仓储 物流 供应链等等
苏宁 线下选货,线上卖货 重点在线下店铺的选择
所以,即是都是电商领域,但是每个模型侧重的点又不一样
领域模型
领域模型是对领域内的概念类或现实世界中对象的可视化表示
业务对象:3种
- 业务对象:表示的一个角色以及她所承担的一些列责任
收银员 职责:计算商品价格 收钱 找零 退换货
- 业务实体:表示的其实是你与业务角色交互所需要的可交付的工件 资源 事件
电商项目:商品 发票
- 业务用例:就是我们的业务角色与业务实体之间如何执行工作流程
业务链路 测试用例
业务对象模型:就是你的业务逻辑流转的过程中需要的所有角色,甚至包括你的业务逻辑流转本身
- 失血模型:对象中只包含get set方法
优点:领域对象结构简单
缺点:肿胀的业务代码,难以维护;无法应对频繁更改的需求
- 贫血模型:
固有行为:对象本身就有的,两只手,两条腿,两只眼睛等(不用持久化的行为)
非固有模行为:对象本身不具有的,比如唱歌,打游戏(需要和数据库交互)
优点;层级结构清晰,各层级单向依赖;对于只有少量业务逻辑的应用来说,使用起来非常自然;开发非常快速,易于理解
缺点:无法良好应对非常复杂的逻辑以及场景
- 充血模型
优点:更加符合面向对象原则,业务逻辑层很薄
缺点:职责不好划分,开发者水平很高;模型中包含了大量的操作,去实例化会增加很多不必要的消耗
- 胀血模型
优点:
优化了分层架构
符合了面向对象设计原则
缺点:
取消了业务逻辑层,直接在domain object上封装事务以及授权,授权很多不属于这个领域对象的逻辑,模型不稳定。
子域
核心子域:核心域是整个业务系统的核心,所有的业务都要围绕着核心业务域展开
如何明确你的业务核心?
精炼业务域
- 领域愿景说明:阿里,让天下没有难做的生意
- 突出核心:留文档,核心业务
- 内聚机制:除了内聚的内容,其他都打包到一块,比如交易保证
- 分离核心:比如支付,那支付优惠就不是核心
- 抽象核心:基本的内容抽象出来,比如付款,下单等流程
通用子域:整个领域都能够用到的子域
比如权限校验,登录校验等
支撑子域:就是不包含核心竞争力的功能,也不包含通用的功能,但是又是必须的支撑
比如限时抢购,团购,秒杀等等
通用语言: 能够正确的,简单的,清晰的表达业务
限界上下文:用来封装通用语言和领域对象,提供上下文环境,保证在领域内的一些术语、业务相关对象等(通用语言)有一个确切的含义,没有二义性,这个边界定义了模型的使用范围。
总结战略设计:
这是一种用高层次视野审视软件系统的方式
战略设计可能会出现的问题
建模方式
频繁的进行战略改动
软件分层架构
严格分层架构:不能跨层访问
松散分层架构:各层随意访问
三层架构设计
分层式软件架构,它和MVC模式不一样
分层架构设计就是为了帮助我们达到高内聚,低耦合复用性设计 拓展性设计
TCP/IP模型 四层模型/七层模型
四层架构设计
用户界面层:显示信息给用户
应用层:线程调度 应用服务 与模型进行与实体无关的业务逻辑
领域层:具体的业务处理
基础实施层: 交互层次
MVC模式与DCI模式
M(Model) V(View) C(Controller)
MVC缺陷:
- 导致控制器冗余
- 控制器对于对象依赖过重
- 对象之间会产生耦合
- 由于spring的存在,其实我们的开发是不符合面向对象的
MVC本质上与DDD有冲突的点:
五层架构以及五层架构变体
DCI架构模式
数据边界与行为边界不一致
- 跨域多个子域,在当前领域聚合,例如中台。哑数据对象,贫血模型 适合项目快速迭代,开发人员不足
- 充血模型带来的负责度
驱动领域在一个流程中会获取到很多不必要的信息,增加了信息暴露的风险
| 用户界面层 | 处理用户发送的ResFul请求,解析配置文件 |
|---|---|
| Scheduler调度层 | 进程管理 线程调度 |
| 事务处理层 | 类级别的事务处理 接口序列级别 |
| 环境层 | 领域与行为绑定 聚合 |
| 领域层 | 领域模型 行为进行建立 |
| 基础实施层 | 基础设施 |
六边形架构
依赖倒置原则:
高层模块不应该过于依赖低层模块,两者都应该依赖抽象
抽象不应该依赖于细节,细节因该依赖于抽象
端口和适配器架构
主适配器 用户的访问 接受用户的输入 调用用户端口返回结果
次适配器 应用的出口端口 ES MYSQL Redis SMS
业务会驱动技术发展
战术设计:根据业务模型进行技术实现,完成软件的开发以及落地
实体
实体核心是用唯一的表示服来定义,而不是通过属性来定义。即使属性完全相同也可能是两个不同的对象。同时实体本身有状态的,实体有演进的生命周期,实体本身会体现出相关的业务行为,业务行为会对实体属性或状态造成影响和改变
User用户对象
主键/唯一索引 userId
姓名 张三
年龄 35岁
籍贯 北京
User1 User2
值对象
它用于描述领域的某个方面没有概念标识的对象,值对象被实例化后只是提供值或设计元素。我们只关心这些设计元素是什么?而不关心这些设计元素是谁。这种对象无状态,本身不产生行为,不存在生命周期演进。
不会进行单独的持久化,对照数据库进行持久化
值对象与实体的区别以及关联
基础但愿
逻辑模型
电商 折扣实体 单独的折扣引擎 多个算法
双十一 凑单商品 持久化对象 订单中一个属性
值对象
值对象 单一属性
public class User implements Serializable{
private Long id;
private String age;
private Date createTime;
private Date updateTime;
private String address;
}
public class Address implements Serializable{
private String province;
private String city;
private String county;
}
工厂与资源库/仓储
实体的最佳创建方式 仓储 工厂
只有聚合根才会需要仓储
- 原则上我们的一个聚合根只会需要一个仓储,不需要给每个实体建一个仓储
- 我们仓储方式最好只有一个save或者byid
工厂
create方法
public static Product create(ProductName name,ProductDec description,Price price){
return new Product(name,description,price)
}
private Product(ProductName name,ProductDec description,Price price){
this.id = productId.newProductIdo;
this.name = name;
this.description = description;
this.price = price;
this.createdAt = Instant.now();
}
资源库
领域事件
领域专家所关心的发生在领域中的一些事件
事件驱动 mq
领域事件 可读性差 代码复杂度增加
spring广播监听
跨应用 Mq
下单(){
修改订单状态();
通知商家();
通知并修改库存();
通知买家();
}
支付成功(){
修改订单状态();
通知商家();
通知头家();
}
申请退款(){
修改订单状态();
通知商家();
通知物流();
通知并修改库存();
通知买家();
}
Q:如果此时需要增加用户画像分析功能,比如分析用户一个月退了几次单,一个月购买了几次等等,这写需求会导致我需要改动下单流程以及退款流程,涉及面很大。那么该如何优化这个设计呢?
A:将领域红所发生的活动建模成一系列的离散事件,每个事件都用领域对象来表示...领域事件是领域模型的组成部分,表示领域中所发生的事件。
// 把事件解藕,使用监听机制
下单(){
修改订单状态();
通知商家();
通知并修改库存();
通知买家();
}
支付成功(){
修改订单状态();
通知商家();
通知头家();
}
申请退款(){
修改订单状态();
通知商家();
通知物流();
通知并修改库存();
通知买家();
}
// 其他地方监听这些事件
@Listen
public void 监听事件(){}
聚合以及聚合根
将实体和值对象划分为聚合并围绕着聚合定义边界。选择一个实体作为每个聚合的根,并仅允许外部对象持有对聚合根的引用。作为一个整体来定义聚合的属性和不变量,并把其执行责任赋予聚合根或指定的框架机制。
聚合是业务和逻辑紧密关联的实体和值对象组合而成,聚合是数据修改和持久化的基本单元,一个聚合对应一个数据的持久化。
DDD 领域层
常规电商的下单业务举例
用户登录网站 -- 商业商品点击 -- 商品详情页 -- 加入购物车 -- 购物车结算 -- 发起订单 -- 后台查询商品库存 -- 拉起支付 -- 用户输入支付密码 -- 用户下单成功提示
上面的流程不一定合理:业务规则的正确性以及一致性 比如网络引起下单的失败或者支付失败。虽然这些问题有对应的解决方案,但是这些问题不应该出现在模型设计的环节
在上述流程中,订单就是聚合根
- 聚合根是实体,拥有实体的业务属性和行为,同时也是聚合的管理者,负责协调聚合内的实体和值对象,按照固定的业务规则,完成业务逻辑。
- 聚合根是聚合对外唯一的接口人,聚合之间以聚合根ID关联的方式接受聚合的外部任务和请求,聚合外不能通过对象引用的方式访问聚合内的对象,需要将关联的整合根ID作为入参,先访问聚合根,再通过聚合根导航到聚合内部实体。
- 如果聚合根被删除了,它引用的实体和值对象就不会存在了
- 聚合根和聚合根所在层的领域服务都可以组合多个实体完成领域逻辑,但为了DDD分层架构的职责单一,聚合根最好只承担聚合管理职能,只实现聚合内实体和聚合根本身相关的业务逻辑,而跨多个实体的复杂领域逻辑统一放到领域服务中实现。
- 领域建模中,可能存在一些独立的找不到聚合根的实体,但可以根据高度依赖的业务逻辑,把这些实体集合也作为聚合处理。
聚合的边界划分设计原则
哪些对象需要聚合在一起?
- 生命周期一致原则 内部的对象应该和聚合根有相同的生命周期
- 问题域一致原则 比如下单和支付就不属于同一个问题域
- 场景一致性原则 操作频率不一致的不放在同一个域,查看,修改
- 聚合尽可能细粒度 性能问题 ;更容易受到并发影响;打聚合拓展性差
领域服务
不能写到实体的业务逻辑,才会写到领域服务中
有些重要的领域操作,不适合归到实体(Entity)和值对象(value object)的类别中,这些操作从本质上讲是活动或者动作,但不是事物,当领域中的某个重要过程或转换操作不属于实体或值对象的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其命为Service。
领域服务是对于实体的补充。
- 约束
1. 不能在领域层写太多的业务逻辑
2. 不能在领域服务里面调用Dao,和仓储
3. 领域服务视为实体的一种补充,里面的属性和逻辑也和实体一样,不能是基础类型得是含有业务含义的值对象
4.领域服务之间可以进行调用,最好是保持父域的领域服务调用子域的领域服务,不允许跨越上下文进行调用
- 涉及到跨越多个实体的操作
比如内部转账,从一个账户实体转到另一个账户实体
- 涉及到与外部系统的交互
调用微信,支付宝转账
Service 确认领域服务的一些规则
Q:是否必须要通过领域服务来操作实体
A:不一定
Q:我们能在领域服务中调用Dao层
A:可以,但是不推荐。保证领域服务简洁
微服务架构设计
Spring Cloud实际是一套Spring Boot的微服务解决方案
垂直架构
垂直架构存在冗余的情况
比如订单系统会需要去用户表查询信息
SOA架构
重复性问题或者冗余的问题得到解决
微服务架构
微服务主要为了解耦
在线网站
订单相关的
用户相关的
商品相关的
需求:我们需要去改变订单相关的税率
这就要求订单业务组进行逻辑改变,同时影响支付,物流等等
这里如果把下单的服务进行拆分的更细,把下单,支付,物流都拆分开来,这样修改的环节就主要集中在下单过程中
RPC
RPC与Http的区别
注册中心
特性
服务治理
服务续约
服务获取
服务调用
服务同步
失效剔除
自我保护
服务下线
动态感知
架构特性
CAP定理 :一致性(Consistency),可用性(Availability),分区容忍性(Partition tolerance)
CP 强一致 数据存储场景
AP 高可用 交互式场景
应用网关
链路追踪
优化系统品经以及生成网络拓扑
日志监控
日志数据分析可视化
断路器
保护系统,控制故障范围
从项目去剖析领域驱动
事件风暴
目的:统一语言的素材,包含对商业流程的共同理解,包含名词使用、责任范围、使用者体验等等
定义:灵活的研讨会形式,用于写作探索复杂的业务领域
使用场景:
-
- 评估现有业务线的健康状况并发现最有效的改进领域;
- 探索新的创业商业模式的可行性
- 设想新的服务,最大限度为每一方带来积极的结果
- 设计干净且可维护的事件驱动软件,以及支持快速发展的业务
事件风暴的优点
快速:事件风暴方法减少了创建圈main的业务领域模型所需的时间。
简单明确:事件风暴不实用复杂的UML,而是将过程分解为技术和非技术利益相关者都可以理解的简单术语。不需要过多准备,直接识别利益相关者就可以开始。
参与:事件风暴目标之一是使建模变得有趣。这是一种动手的领域建模方法。
有效:事件风暴不是数据建模,但通过大家的讨论可以快速实施和验证的行为模型。同时为了获得最佳结果,团队应将事件风暴与面向对象领域驱动设计的实施相结合。
设计一个DDD的电商项目
- 通过界限上下文从全局的角度规划整个系统的业务模块
- 逐步细化对每个模块开展事件风暴会议进行领域建模
- 逐步落实到每个模块的数据库设计与微服务设计以及需要涉及的分布式技术与云端部署
现在以一体化商城平台项目为例,该项目是一套集物流,仓储,电商为一体的智慧电商平台。同时服务于广大的中小型商家,商家可以在平台进行申请开店。平台提供对应的第三方物流,仓储以及一系列交易保证功能。
角色纬度进行划分子域
- 移动用户端+pc端电商网站
- 移动商家端+商家端后台
- pc端后台管理
- 大数据管理后台
DDD
为什么使用
DDD方法的核心是不断将问题分解,把大问题分解为小问题,把大的业务范围分解成小的领域,分而治之。
我们面临的可能是个大业务,并且无从下手,那么就需要对业务进行分解,雏形:多少业务功能,每个功能--接口
分解成为高内聚的小领域,可以让我们的业务有边界,但是领域是实际的边界,这就是领域驱动设计的核心。
方法与目标
将我们的业务划分为边界清晰的模块,DDD只是方法之一
不必纠结于局部
由于项目庞大,导致整个项目无限划分
不用过于纠结,根据功能重要性,核心功能可以详细点,其他的子域可以粒度粗点
领域与数据
领域对象与数据对象,区别:值对象的存储方式
// 实体中只包含对象基本信息的可以看作领域对象
public class FootBallPlayer{
private int id;
private String name;
private float heigh;
private int age;
}
// 实体中包含对象基本信息同时还包含了其他领域对象,这种对象可以看作数据对象,更多的是业务含义
public class FootBallPlayer{
private int id;
private String name;
private float heigh;
private int age;
// 包含比赛成绩信息等等
private FootBallGame footBallGame;
}
抽象与灵活
核心是找相同的地方,对不同事务的相同地方提取公因式
四色建模法
- 时标对象:事实的不可变性;责任可追溯性;
- 参与方,地,物
- 角色对象
- 描述对象
项目白皮书
一、大型互联网DDD分布式微服务马士兵"好借好还"金融项目介绍
马士兵好借好还是一个网络借贷信息中介服务平台,也就是我们所说的金融超市,为个人投资者、个人融资用户和小微企业提供专业的线上信贷及出借撮合服务。主要是B2C的模式,并且所有的资金业务均为三方监管,符合相关国家的法律。
从技术角度出发,整个项目主要是金融项目,整个项目体系主要注重的高一致性以及信息安全性。所以整个项目中有很多数据需要加密解密操作。并且由于其中很多的流程会比较复杂。因为涉及到不同的借贷,所以这边我们会选用领域驱动设计(DDD)做为整个项目的工程方法论。并且整个项目采用微服务的架构风格进行设计。
二、信用贷款平台的类别
1、银行系
- 优势:第一,资金雄厚,流动性充足;第二,项目源质地优良,大多来自于银行原有中小型客户;第三,风险控制能力强。如恒丰银行、招商银行等旗下都有信用贷款平台。
- 劣势:收益率偏低,预期年化收益率处于5.5%-8.6%之间,略高于银行其他理财产品,对投资人吸引力有限。
2、国资系
- 优势:拥有国有背景股东的隐性背书,兑付能力有保障,业务模式较为规范,从业人员金融专业素养较高。
- 劣势:缺乏互联网基因;项目标的较大,起投门槛较高;且产品种类有限,多为企业信用贷;较为谨慎,层层审核的机制严重影响了平台运营效率;收益率不具有吸引力。
3、民营系
民营系平台数量最多,起步最早,但鱼龙混杂,不胜枚举。
- 优势:普惠金融,手续便捷;门槛极低,投资起点低最低起投门槛甚至50元;强大的互联网思维,产品创新能力高,市场化程度高;收益率高,投资收益率具有吸引力。
- 劣势:风险偏高,资本实力及风控能力偏弱,跑路及倒闭的高发区。
三、业务流程
1、投资人
- 有一定资金量,希望在平台上找到合适的投资项目,获取投资回报的用户
2、借款人
- 信用审批合格,并且可以直接计算出额度超过0的用户
3、资金池
- 资金池:一个大池子放钱,一边存进来(入水管),一边贷出去(出水管)。不管是张三的钱、李四的钱、还是王五的钱,只要进到池子里,就都叫池子的钱了。银行就是典型的资金池。
4、资金托管平台
- 第三方存管模式:“第三方存管”的全称是“客户交易结算资金第三方存管”。这里的第三方存管机构,目前是指具备第三方存管资格的商业银行。银行的流入资金成本低,风控体系较完善,资金池子足够大,而且是国家背书,不会跑路。
- 说明:由于我们是教学使用,无法申请到正式的资金托管平台的支持,所以我们根据资金托管平台API接口文档,自行开发模拟一套API接口来满足业务需要,业务过程与实际开发基本一致。
四、课程介绍
整个课程采取DDD做为项目的工程方法论,并且由于项目是金融项目,所以整个项目的体系会采用Spring Cloud Alibaba体系,而非奈飞体系,并且组件方面整体采用偏向阿里体系组件,并且项目重点在于微服务架构风格与DDD的实战落地。课时安排还未确定,课程更新完毕后会对课程介绍做再修改。
五、技术架构图
六、业务架构图
七、网络拓扑图
八、业务流程梳理
九、开发环境以及操作
一、前置知识体系
Java基础、HTML、CSS、JavaScript、Spring、SpringMVC、MyBatis、SpringBoot、SpringCloud Alibaba MySQL、Redis、RocketMQ、nginx、idea、maven、VMWare 虚拟机、CentOS7.X 操作系统、领域驱动设计、架构设计基础
二、技术栈
1、后端
- SpringBoot 2.2.5.RELEASE
- SpringCloud Hoxton.SR8:微服务基础设施 - 服务注册、服务发现、服务熔断、微服务网关、配置中心等
- SpringCloud Alibaba 2.2.2.RELEASE
- MyBatis Plus:持久层框架和代码生成器
- Lombok:简化实体类开发
- Swagger2:Api接口文档生成工具/apidoc,这个还没定下来
- Logback:日志系统
- alibaba-easyexcel:Excel读写
- Spring Data Redis:Spring项目中访问Redis缓存 / 项目中会对于Redis的使用进行再封装
- Fegin: 基于Http协议的客户端,用来实现远程调用
- Spring Task:定时任务
2、数据库和中间件
- MySQL 5.7:关系型数据库
- 管理工具:Navicat
- Redis 5.0:缓存技术
- 管理工具:RedisDesktopManager
- RocketMQ 4.7.0:消息中间件
3、三方接口
- 阿里云短信:短信网关 (可能会更新为华为云)
- 阿里云OSS:分布式文件存储 (可能会更新为华为云)
- 资金托管平台API对接:未定,正在对接中
4、前端
- Node.js: JavaScript 运行环境
- ES6:JavaScript的模块化版本
- axios:一个发送Ajax请求的工具
- Vue.js:web 界面的渐进式框架
- Element-UI:前端组件库
- 模块化开发:解决javascript变量全局空间污染的问题
- NPM:模块资源管理器
- vue-element-admin:基于Vue.js的后台管理系统UI集成方案
- NuxtJS:基于Vue.js构建的服务器端渲染应用的轻量级框架
十、团队构成以及背景
这个很重要,有多少人干多少事,如果团队配比不正确,很难达到满意的效果。
高配版:
产品团队:一位产品总监,产线上至少3-4位产品经理,每个产品经理配备4位左右产品专员或者策划
研发团队:一位技术总监,一位架构师,3-4位技术经理,3-4位技术专家,每个技术经理带2-3个小组进行模块开发,每组1位高工或者资深,2位中级,2-3位初级。
低配版:
产品团队:一位资深产品经理,配备4位左右产品专员或者策划
研发团队:一位架构师,10+研发,其中高工比例3-4位
一、建模与设计的流程
建模与设计的流程实际上分为4个部分,分别是挖掘用户故事,确定核心愿景,建立通用语言,战略设计,战术设计。其实这四个步骤是脱离于原本的领域驱动设计的思想的,因为原本的领域驱动设计是在战略设计当中进行1.2步操作的,但是由于实际情况的不同,我们很难直接去进行战略设计,所以我会将挖掘用户故事,确定核心愿景,放在立项初衷,因为一般这个步骤是由老板确定的,或者核心产品负责人确定,与技术无关性较大,而通用语言的建立也是立项之初就应该确定的事情。所以这边我会将技术业务完全区分开来。
而一个软件从市场调研,到需求分析,到立项,到竞品分析,再到技术方案架构探讨。
其次,我们也并不是建立完成我们的架构之后,就可以高枕无忧了,由于项目的需求变更,市场的变化,我们经常会需要改变原本的计划,比如可能进行到项目的战术设计阶段,由于一些原本没有考虑到的点,会导致我们回过头来对原本战略设计的模型进行优化,所以这边的话,会有一个概念,叫做设计涡流,而建模过程中产生的设计涡流,则被叫做建模涡流。
正式由于建模涡流的产生,我们会发现,我们的真实的业务设计并不是说一蹴而就,瀑布式,搞定就完了,而是一个不断优化,解决问题的过程。
实际上6降低到支付模块。这样开发人员的关注面就会变短。那么
一.挖掘用户故事,确定核心愿景
用户故事核心要素:问题空间的描绘,文字表达,讨论+图形表达
1、问题空间的描绘
其实这个问题的解决来源于我们的项目设计,为什么这样说呢。他有点类似于我们通用语言设定的初衷,只不过他的范围是我们的领域。这边给大家列举一个等式。
问题空间=问题域=领域=业务边界
这样聊大家应该好理解很多。
- 一个领域代表了一个问题域的边界,也可以理解为是一个业务的边界。也就是说它是用来解决我们的业务开发能力的。通常我们大家交流都比较喜欢用业务这一词,比如这块业务,那块业务,业务的边界,我是一个业务开发人员(区分于我是一个中间件开发人员)。而领域一词,相对比较抽象,不是那么容易懂。
- 领域既然是一个边界,所以可以划分领域的大小,即领域划分,划分出来的子领域简称子域,每个子域对应一个小的问题域和和小的业务;当然,不同的子域的重要性也是不同的,所以才有了核心子域、支撑子域的说法,这点显而易见。
2.文字表达
文字表达尽可能表达用户需求
3.讨论+图形表达:
不确定的东西,尽可能让研发,产品,运营等相关人员进行讨论,但是不是无意义的讨论,需要用图形将讨论的东西固化下来。
反例:
- 问题:研发经常抱怨产品经理产品需求不明确,导致后续研发出现问题。比如:让用户能够便捷的注册,并且能够审核信息。这样的需求做出来的东西肯定是千奇百怪的。
- 解决:正确的解决问题,描述清楚用户的核心需求。比如用户必要的步骤,核心的需求。比如用户需要填写 哪些信息,用户需要填写哪些信息,尽可能明确,并且产品经理也需要考虑到后续的改动,比如后续可能会增加哪些字段,需不需要预先留位置。
实际上我们的开发人员肯定是希望产品最开始的时候就已经完成整个项目的设计,最好是后续不再进行修改,并且每个项目的架构师,最好可以一开始就可以定型整个项目的架构,后期也不在修改,并且技术难点都被架构师攻关掉,这样的产品,开发人员开发起来才更加舒服,但是这样的产品是没有活力的,也是没有增长的。一成不变的产品对于市场是没有吸引力的。说不定明天就会有更好的产品对他进行替代。而没有性能上限的项目也是不存在的。迟早有一天你的架构会不再适用。所以本身开发人员跟市场变化就是一个对立面或者反向对标。而架构师其实算是技术领头人,所以架构要做的事情就是尽可能消磨掉这种矛盾。也就是用技术手段整合双方。让专门的问题由专门的人去解决。把问题从项目维度降低到某个领域,某个能力。比如从整体金融项目,降低到支付模块。这样开发人员的关注面就会变短。那么开发人员所处理的问题的复杂度也会降低。
由用户故事可能会引发的问题:
- 代码写好之后修改需求
- 需求文档不细致,关键点不明确,出现矛盾点的时候产品也给不出答案
- 产品拍脑袋定需求,不考虑实际情况
解决问题的手段:
围绕用户故事进行讨论,根据讨论的图形以及文字结果,产品进行相应的设计。产品的交互设计,开发的程序逻辑包括表结构设计。
1.真正的理解用户故事
在软件开发中,用户故事是一种对软件系统特性的非正式的自然语言描述,是敏捷软件开发中从终端用户的角度对软件系统特性进行捕捉的一种方式。用户故事描述了同类型的用户需要什么以及为什么需要,它可以帮助我们创建需求的简单描述。
在软件开发和演进过程中,随着产品和开发对产品认识的加深,需求总是在不断变化,所以,过早地进入需求细节以及对细节的描述,是一种时间上的巨大浪费。从这点来说,用户故事提供了一种恰到好处的粒度,使得产品在需求分析阶段能够极大地节约时间,并且使产品和研发人员始终把注意力集中在关键点,避免他们过早地陷入细节以及被细节所局限,同时给产品功能留出了讨论空间,从而使产品有机会在讨论过程中得到优化。
实际上,描述好的用户故事是产品经理的活,但是由于产品的不给力,做为架构师,有责任去纠正以及规范产品经理所需要去进行的操作。这也是架构师的职责所在。并且,领域驱动设计本身也是一个架构方法论
开发过程中用户故事的3W要素以及3C原则
什么是3W要素
用户故事=用户+故事=人+故+事,那就是一个人因为什么原因要做什么事,提炼出来三要素就是who、why、what。从需求角度描述就是一个用来确认用户和用户需求的简短描述。
一个完整的用户故事包含三个要素:
角色(who):谁要使用这个
原因(why):为什么要做这个活动
活动(what):要完成什么样活动
价值(value):为什么要这么做,这么做能带来什么价值
举例:借款人 需要借款 我们直接生成标的去进行借款 简单快捷
什么是3C原则
用户故事的构建一般来说有三个环节:
1.简单描述用户需求;
2.围绕简单描述进行讨论;
3.明确如何验证。
分别对应用户故事的三个元素,也就是3C:Card(卡片)、Conversation(谈话)、Confirmation(验证)。
(1)卡片(Card):用户故事一般在小卡片上写着故事的简短描述,规则和完成标准。
实际上,他也对应了我们的3W,因为简单描述用户的需求,实际上就代表着用户行为的总结
我们一开始就可以通过卡片去浓缩我们的用户需求,点出精髓。
(2)交谈(Conversation):用户故事背后的细节来源于和客户或者产品负责人的交流沟通;确保各方对故事的理解正确。并且最好用相关的东西进行记录,比如图形或者文字,记录交谈的内容。而我们通用语言的确定,很大程度也来自于我们的交谈话术。
(3)确认(Confirmation):通过验收测试确认用户故事被正确完成。
INVEST原则
好的用户故事除了格式规范,要素完整外,还应该遵循INVEST原则:Idependent(独立的);Negotiable(可协商的);Valuable(有价值的);Estimatable(可评估);Small(小的);Testable(可测试的)。
1. Idependent(独立的)
要尽可能的让一个用户故事独立于其他的用户故事。用户故事间保持独立性不仅便于排列和调整优先级,使得发布和迭代计划更容易制定,便于独立地理解、跟踪、实现、测试以及频繁交付,也使得用户故事的大小估算所涉及的范围更清晰,从而估算偏差更小。
2. Negotiable(可协商的)
一个用户故事的内容要是可以协商的,用户故事不是合同。一个用户故事只是对用户故事的一个简短的描述,不包括太多的细节;具体的细节在沟通阶段产出。一个用户故事带有了太多的细节,实际上限制了用户、团队的想法和沟通。
3. Valuable(有价值的)
每个故事必须对客户具有价值(无论是用户、购买方还是公司内部角色)。用户故事对于最终的用户是有价值的,因此应该站在用户的角度去编写,描述的是一个一个的feature,而非一个一个的task。
这个特点促进团队的开发和测试成员由传统的指令式工作方式向自驱动的价值导向工作方式转变,使团队中的每个人知道自己每天做的工作价值。
4. Estimatable(可评估)
计划会议里面一个很重要的环节,那就是故事点的估计。实际上就是对要开发的User Story进行一个粗量级的估算,以便于团队能够知道这个user story的复杂度(工作量)。
重点放在当前迭代里能否按照该用户故事的接收条件和团队定义的DoD(完成标准)来完成这个用户故事,如果不能完成,给出理由,由PO来决定是否拆分或者重新设计用户故事。
让开发者难以估计故事的问题来自:对于领域知识的缺乏(这种情况下需要更多的沟通),或者故事太大了(这时需要把故事切分成小些的)。
5. Small(小的)
一个好的故事在工作量上要尽量短小,最好不要超过10个理想人/天的工作量,至少要确保的是在一个迭代中能够完成。用户故事越大,在安排计划,工作量估算等方面的风险就会越大。
6. Testable(可测试的)
一个用户故事要是可以测试的,以便于确认它是可以完成的。如果一个用户故事不能够测试,那么你就无法知道它什么时候可以完成。一个不可测试的用户故事例子:软件应该是易于使用的。
三个准则
一个用户、完整价值、不依赖。
1. 一个用户
只包含一个用户,因为多个用户常常有细微的差别。一般是典型的用户,常常有共同的某类需求。
2. 完整价值
完整地交付一个客户价值。一个完整的用户故事意味着这个故事完成后,用户可以达成一个明确的、有意义的目标。
3. 不依赖
依赖性的三种常见类型是:重叠、顺序和包含。
总体上来说,故事之间功能点相互重叠是需要避免的;顺序关系是现实存在,在多数情况下可以通过一些手段解决;包含关系对复杂系统是有帮助的,对排定发布和迭代计划的影响需要注意。
(1)重叠依赖
重叠依赖是带来最多困扰的依赖形式,特别是多个用户故事包含多个不同的重叠部分时,很难找到一组用户故事可以代表该最小可行产品的功能集合,该集合应该包含且仅包含一次需要的功能。
解决方式:
将重叠部分单独剥离出来做为独立的用户故事;合理拆分用户故事,并且将重叠部分只保留在一个最有内聚性的用户故事中;使用Scrum开发模式。
(2)顺序依赖
顺序依赖是指要使某用户故事完成,另外的一个或多个用户故事必须在它之前完成。顺序依赖通常是无害的,而且有一些方式可以减轻这种依赖。
从敏捷开发的角度,整个系统是从初始的最小可行产品逐步演化为强大的产品,后面的每一步是建立在前面的基础之上的。
但从另外的角度,不必要的顺序依赖使得排列和调整优先级变的比较困难,进而影响制定发布和迭代计划,也使得用户故事的大小估算更难以把握。
解决方式:
一个迭代内的用户故事尽量做到没有内在依赖;保持迭代之间只有单向依赖,在时间上只有后面迭代的故事对前面迭代故事的单向依赖(前向依赖);剥离出核心依赖作为独立的故事,不要把有依赖和无依赖的需求混在一个故事里。
(3)包含依赖
包含依赖是指在组织用户故事时使用有层级的管理,比如常见的特性-故事两级管理,一个特性包含多个用户故事,这样就构成了特性对其属下故事的包含依赖。
解决方式:
用户故事一级用来做迭代计划,避免用特性一级做粗粒度迭代计划,特性一级可以用来做发布计划;特性一级同样可以进行拆分,直至拆分到最小市场化特性的程度,并将其包含的用户故事分别归到新拆分出的特性中去;遵从最小可行产品的理念,一个特性分多个用户故事多个迭代实现,每一个迭代可形成潜在可交付或者提供内部或外部反馈。
1.构建项目的用户故事
当马士兵"好借好还"金融项目立项之后,我们开天辟地要做的第一件事情,就是梳理出最顶层的用户故事。在团队中,撰写用户故事的一般是产品经理。在"好借好还"金融项目案例中,我们的产品经理梳理出来以下这些最主要的项层用户故事,迈开了系统分析和建模工作的第一步,为我们的后续工作打下了基础。
(所有用户故事的详细描述,都是建立通用语言的关键资料,因此,我们将其收录进项目通用语言文档,方便回溯)
1.贷前流程
- 卡片
做为借款人,我希望能够快速审核资质,得到额度下放,以便我顺利借款
- 谈话
(谈话过程需要收录进项目的通用语言文档)
-
- P:做为借款人,我希望能够快速审核资质,得到额度下放,以便我顺利借款
D:这里借款人跟投资人需要设定用户权限吗?还是都作为普通用户,只不过有两个入口,一个借款,一个投资。
P:在主观上,我们需要进行区分,但是我希望是以入口的方式,也就是说,不必对用户进行相应的标签。因为借款人也许也会有小额投资标的的需求。
D:那么我们以后将借款人与投资人统称用户,只不过分为借款角色以及投资角色可以吗?
P:可以的。
D:那么用户绑定资管平台账号失败或者未绑定怎么办?
P:可以让用户重新跳回绑定界面。因为如果想进行相应的操作,就必须绑定资管平台账号,受平台监管。
D:那么贷款人资质不通过呢?
P:可以放任不进行任何操作或者拉进黑名单
D:请不要给我不确认的答案,我需要确定的回答
P:那么直接拉入黑名单
D:额度可以为0吗?
P:自然是可以的,因为后续可能由于信用提升增加额度
D:有审核通过借款失败的情况吗?
P:有的,比如放款额度不够或者额度为0.
D:那么这种情况需要拉黑吗?
P:这个我不确定,我需要跟运营部门确认一下
P:O 我们在进行好借好还项目的建模,想咨询一些关于贷前的一些问题,比如用户绑定绑定资管平台账号并审核资质成功后,由于放款额度或一些问题失败了,那么这个时候需要拉黑吗?
O:当然不用,因为每一个用户都是珍贵的,可能只是操作失误而已,直接给个提示后,回到首页即可。
- 验收测试标准
假设我是一名借款人,绑定资管平台账号并审核我的资质成功之后,我希望顺利的看到我的额度信息。并且我希望放款成功。
其他顶层用户故事均在用户流程图中,为了不浪费大家时间,这里不再演示。
2.使用合适的方法论以及工具去分析用户故事(Domain Storytelling)
什么是Domain Storytelling?
DomainSorytelling是一种领域分析建模方法,通过这种方法,产品和研发人员可以利用语言学习的相同原理建立或者学习一个领域的通用语言,并且建立领域模型。
2.2 domain storytelling图和相关工具
在storytclimy过程中,一方(通常是架构师)聆听另一方(通常是产品经理或者领域专家)以主谓宾的句型(谁做了什么)讲述用户故事的流程,进行问答和讨论,并以图形的方式快速复述出来。
domain-story-modeler工具官方链接:github.com/WPS/domain-…
这个工具是一个可以在线实践Domain Storytelling的工具,我们可以通过这个工具在线构建出我们的用户故事图形化。
我们去尝试一下
二.建立通用语言
一种描述模型并且基于模型的语言
让团队的成员都在一个维度上进行沟通
不仅仅是文档,代码中也会存在通用语言。
通用语言:
通用语言的使用:
1.类跟操作的名称
2.施加于模型之上的规则或者约束
3.应用于领域的模式
三.战略设计
1.领域划分
1)什么是领域划分
- 领域划分就是分离你的关注点,让原本整体的业务以问题空间为维度进行拆分,便于问题的解决。
- 子域就是领域中某一类问题以及他相关联的问题
比如:
资管平台账号绑定 1 审核个人资质 1
额度审批 1 上架标的物审核 1
投标 1 放款 1
单据 1 资管平台异常告警 1
领域划分与传统开发模式思维带来的弊病
1.问题点与领域知识重叠
举例:投标跟放款实际上都会涉及到支付,那么这个时候我们的支付可以走同一套支付逻辑
2.模型重叠
都涉及到支付域,但实际上确实两个人开发
如何进行领域划分呢?
实际上最好的方式是借助用户故事进行领域的划分。
举例:
那么我们就可以根据这个内容进行划分
比如贷前流程
当然,经过一系列流程后,我们其他的用户故事也有可能会有其他的域。这个时候进行合并即可
四.战术设计
项目名称:
com.msb.ddd
项目包构建:
manage模块(管理)
balance模块(结算)
business模块(交易)
Auditing模块(审核)
payment模块(结算)
Outside模块(第三方对接)