luckydraw-ddd 04-模板模式处理抽奖流程
领域开发04-模板模式处理抽奖流程
学习目的
【1】基于模板设计模式,规范化抽奖执行流程
【2】掌握抽奖流程和相应的业务逻辑
【3】以抽奖业务流程功能开发为参考,进一步了解基于ddd的功能开发流程
参考分支 | 210828_xfg_subtractionStock |
构建分支 | 220117_03_subtractionStock |
1.需求分析
基于模板设计模式,规范化抽奖执行流程。包括:提取抽象类、编排模板流程、定义抽象方法、执行抽奖策略、扣减中奖库存、包装返回结果等,并基于P3C标准完善本次开发涉及到的代码规范化处理
2.项目结构设计
开发说明
【1】插件配置: IDEA P3C 插件 Alibaba Java Coding Guidelines
,统一标准化编码方式
【2】定义 Preferences | Editor | File and Code Templates -> File Header
/**
* @description:
* @author:
* @date: ${DATE}
* @Copyright:
*/
【3】数据表信息调整:调整表 strategy_detail
添加 awardSurplusCount
字段,用于记录扣减奖品库存使用数量
alter table strategy_detail add awardSurplusCount int default 0 null comment '奖品剩余库存';
【4】模板方法设计模式优化DrawExecImpl抽奖过程方法实现
模板模式应用
重温模板模式
参考小傅哥“模板模式”:https://mp.weixin.qq.com/s/3u1gCJBYLna8qwV9dUgpmA
应用实践
【1】开发说明
“抽奖流程标准化”概念:将一个抽奖的流程进行定义,不同抽奖策略在遵循定义的抽奖流程的前提下扩展不同的业务功能逻辑
结合提供的思路将抽奖流程概述为下述内容
1. 根据入参策略ID获取抽奖策略配置
2. 校验和处理抽奖策略的数据初始化到内存
3. 获取那些被排除掉的抽奖列表,这些奖品可能是已经奖品库存为空,或者因为风控策略不能给这个用户薅羊毛的奖品
4. 执行抽奖算法
5. 包装中奖结果
传统的实现方式可在一个类中顺序定义开发相应的代码逻辑,但基于这种的代码实现过程并不利于维护及流程节点扩展,因此通过构建“模板”概念,将业务流程标准化
回过头思考在抽奖策略领域模块开发的时候在代码开发思路梳理的时候有简单捋了一下抽奖流程的思路,而在这一节是要将这个思路进行优化、扩展,简单构建下述图示
结合提供的思路,进一步对抽奖流程进行细化、标准化,对比传统抽奖流程实现和模板模式构建:
细化实现说明:
类 | 说明 |
---|---|
DrawConfig | 抽奖策略:通过map封装 抽奖策略有两种注册方式:手动注册、动态注册 可参考:SingleRateRandomDrawAlgorithm、EntiretyRateRandomDrawAlgorithm |
DrawStrategySupport | 提供抽奖策略数据支持,便于查询策略配置、奖品信息。通过这样的方式隔离职责 |
IDrawExec | 抽奖相关接口定义 |
AbstractDrawBase | 抽象类定义模板方法流程 在抽象类的 doDrawExec 方法中,处理整个抽奖流程,并提供在流程中需要使用到的抽象方法,由 DrawExecImpl 服务逻辑中做具体实现 |
DrawExecImpl | 抽奖活动实现类 |
此处需要理解的点:
1.抽奖策略数据的封装
DrawStrategySupport
以继承的方式 DrawConfig
扩展抽奖策略属性
DrawStrategySupport
中则是定义了数据操作相关方法:引用lottery-domain的repository层,提供数据操作入口
2.模板模式构建的基础
1.定义抽奖流程标准化的方法 2.根据抽奖流程定义相应“节点”的接口(这些节点针对通用的场景可以是方法构建,针对扩展的场景则可以是接口定义)
AbstractDrawBase
作为模板模式构建的关键,提供定义了doDrawExec
构建抽奖流程标准化的引用,并提供抽奖流程节点的“扩展”入口或者是通用方法
3.业务流程的完善
代码结构梳理完成,随后则可通过相应的实现构建dao、service相关实现(其中还涉及model、vo、enums等相关引用的变动),以及DrawResult结果封装的扩展(引入一个新的构造函数封装抽奖结果)
结合上述设计模式的构建和思路,大致的代码框架构建完成,则可进一步结合场景需求完善业务流程,在实现的过程中涉及lottery-common、lottery-infrastructure、lottery-domain的改动,变动说明简述如下:
lottery-common:通用变量的定义(此处针对抽奖流程中涉及到的状态枚举进行定义)
lottery-infrastructure:dao层设定
lottery-domain:service.draw(模板模式改造)、repository(数据仓储,引用lottery-infrastructure:dao)
lottery-interfaces:接口定义,mapper相关映射文件定义(resources)
4.抽奖策略动态注册改造
lottery-domain中引入lottery-common模块依赖,在lottery-common模块中引入相关常量配置Constants封装中奖模块相关枚举定义(StrategyMode-策略模式、DrawState-中奖状态),此处为了实现对策略模式相关算法动态注册,将StrategyMode单独定义出来
5.测试
初始化数据:添加数据进行测试,或者直接使用对应分支提供的sql重置数据库,用于测试(若不想数据覆盖则备份)
在lottery-interface中进行测试,在src/test区域中构建测试
测试结果(策略1)
【2】问题说明
遇到空指针错误打断点检查,查看是否是组件注入问题还是数据处理空指针异常
drawAlgorithm
空指针则说明并没有从抽奖策略组中匹配到适配的内容,这种情况有两种可能,一种是抽奖策略的数据没有和DrawConfig中drawAlgorithmGroup对上号,所以在初始化抽奖策略内容的时候无法匹配到指定的算法(要么数据没有对照,要么抽奖策略组为空)
// 抽奖策略组定义
Map<Integer, IDrawAlgorithm> drawAlgorithmGroup => strategyMode,抽奖算法
另一种情况则可能是动态注册抽奖策略失败导致的空指针异常,需依次检查相应的实现和注册
1.StrategyModeEnum枚举定义
2.StrategyMode自定义注解
3.将静态引入改成动态注册改造DrawConfig
4.抽奖策略算法动态引入:定义抽奖策略算法,并通过StrategyMode注解注入
# 随后系统启动则会相应扫描相应注解的内容并注入
3.问题思考
dao&mapper:理解多模块工程构建的思路,但是将dao数据访问接口定义(lottery-infrastructure)和实现(lottery-interfaces)拆在不同层是基于怎样的考虑?