文章目录
- 1.DDD结构基础
- 2. 抽奖领域
- 3. 发奖领域
- 4.活动领域
- 5. 支撑领域
- 应用层编排
1.DDD结构基础
-
包含:
接口层、应用层、领域层、基础层;通用包、接口层、接口定义。
接口层:实现RPC接口定义,引入应用层服务,封装具体的接口。
应用层:逻辑包装、编排、任务,领域事件的发布和订阅。
领域层:封装具体的业务功能的实现,它们是聚合、充血的- model,用于提供vo、req、res 和 aggregates 聚合对象
- repository,提供仓储服务,其实也就是对Mysql、Redis等数据的统一包装。
- service,是具体的业务逻辑实现,对外提供接口
基础层:数据库、Redis、ES
通用包:通用的对象、常量、枚举、异常
接口定义:描述RPC接口文件,用于打包后外部引入pom配置
-
为什么要定义RPC接口层:
因为使用 RPC 框架的时候,需要对外提供描述接口信息的Jar
包让外部调用方引入,所以RPC 需要暴露出来,而 DDD 的系统结构又比较耦合,所以进行了模块化的分离。 -
知识总览
表信息包括:活动表、奖品表、策略表、规则表、用户参与表、中奖信息表等
- Dubbo怎么用:
提供方这里打包成jar包,调用方这里引入pom,用注解@reference注入,就可以调用了。
2. 抽奖领域
口述:抽奖这个领域是可以独立配置和使用的领域模块,没有引入活动id,可以满足不同业务场景的灵活调用,使用了策略模式根据传入的策略选择不同的抽奖算法。比如单体概率、整体概率
划分了两张表,策略配置表和策略明细表,来满足不同业务场景的灵活的调用,比如说:有些业务场景直接进行抽奖反馈中奖信息发送给用户,但还有一些场景用户下单支付才满足抽奖条件,对应的奖品是需要延时到账的,避免用户在下单后又进行退单,这样造成了刷单的风险。
-
策略配置表和策略明细表,它们的关系是1v n。
-
两种抽奖策略算法
单项概率抽奖和整体概率抽奖:为了配合不同的玩法,当1个奖品被抽空了以后,那么再抽奖时,是剩余的奖品总概率均匀分配,还是保持剩余奖品的中奖概率 -
抽奖算法实现:
单体概率:采用的斐波那契散列算法,散列效果比hashmap更好,O(1)复
杂度下完成
总体概率:随机0-1值,看一下这个随机的概率在哪个奖品区间,表示中哪个奖
-
模板模式处理抽奖流程
使用模板的设计模式,规范抽奖的执行流程,包括:校验抽奖策略有没有初始化(单体概率的话需要初始化,使用斐波那契算法来初始化概率数组)、获取不在抽奖范围内的奖品(库存为空、风控、临时调整)、执行抽奖(策略模式:根据传入的策略选择不同的实现,执行抽奖拿到奖品id,扣减库存{使用redis分布式锁,有更好的并发性})、包装中奖结果。
3. 发奖领域
有四种奖品发放的方式,1:文字描述、2:兑换码、3:优惠券、4:实物奖品,使用简单工厂,将全部发奖方式放入map中(@PostConstruct完成初始化),在发奖时根据查到的奖品的类型到工厂取对应的发奖方式。
4.活动领域
活动创建、活动状态处理、用户领取活动
- 活动创建:创建活动涉及到多张表(活动信息、奖品信息、策略信息、策略明细),要在一个事务下进行落库。
- 活动状态的处理:活动状态的处理用到了状态模式进行处理,活动状态包括(1编辑、2提审、3撤审、4通过、5运行(审核通过后worker扫描状态)、6拒绝、7关闭、8开启),在抽象类中定义了所有状态流转的抽象方法,子类也就是所有的状态类重写里面的方法,它们各自都有自己的状态的改变逻辑,比如说编辑状态可以变为提审状态、可以变为撤审、关闭状态,那只有这些可变的状态才会进入DB,进行活动状态的修改,不可转变的状态就不会进入,定义好这些状态类后,将它们放入map中,在对外提供的接口中,根据当前状态在map中拿到对应的状态类,就实现了状态的改变。
- 用户领取活动:使用模板模式,抽象类定义模板,模板流程包括:1. 查看有没有未执行抽奖的活动,有的话说明用户已经领取了活动但是抽奖失败(user_take_activity 用户领取活动记录表state = 0,领取了但抽奖过程失败的,可以直接返回领取结果继续抽奖) 2. 活动的校验(日期、库存、状态)、扣减活动库存、添加用户领取信息(扣减个人已参与次数、添加用户的领取信息)、封装结果(返回策略id,用于后续抽奖)。扣减个人已参与次数(用户参与次数表)和添加用户的领取信息(用户参与记录表)是要保证事务性的,
为了保证在多个DAO上使用路由并且保证事务不失效这里采用的是比较简单的方法
:吧切换路由的操作放在事务开始前,事务也采用编程式事务。- 易错点:扣减库存与添加用户领取信息是在两个库中,按道理来说扣减活动库存失败了因为恢复掉用户的领取信息,这里的话采用redis扣减库存,不能保证完全恢复正确,其实类似于超卖,不能让用户多领取,可以接受一个活动没用户领取。
5. 支撑领域
包括:ID的生成、分库分表、vo2dto方法、Redis
- ID生成:使用策略模式将三种生成ID的算法统一包装,由调用方决定使用那种生成ID的策略,ID可以用在订单号、策略ID、活动id,包括雪花算法(hutool)、日期算法(仅支持很小的调用量,用于生成活动配置类编号,保证全局唯一)、随机算法
- 分库分表:使用EnvironmentAware 解析配置,在自动配置类中创建数据源,在aop切面的拦截中,拦截需要分库分表的方法,分库分表的逻辑是类似于hashMap,将路由字段进过扰动函数计算出索引,再折算到具体的库表,当然这部分东西也不一定放在切面中,可以做成一个bean,可以配合编程式事务,分库数、分表数存到ThreadLocal中,用于传递在方法调用过程中可以提取到索引信息。
Mybatis 拦截器处理分表,通过拦截 SQL 语句动态修改添加分表信息,再设置回 Mybatis 执行 SQL 中,分表这部分主要就是在表后加入分表索引来确定是哪个表。
应用层编排
功能:调用领域服务功能,编排抽奖过程,包括:对于活动的领取、抽奖的操作、中奖结果的保存、处理发奖。
首先查一下有没有领取了活动但是执行抽奖失败的活动单,有的话拿到这个活动对应的策略id,再由该策略经过不同的概率算法计算出获奖的信息,再保存中奖的结果:保存的时候要同时修改两张表:用户领取活动记录表(state改为1)和用户奖品表。使用编程式事务控制。