【luckydraw-ddd】领域开发05-简单工厂搭建发奖领域
【luckydraw-ddd】领域开发05-简单工厂搭建发奖领域
学习目的
【1】数据库规范:数据库整改、dao层调整
【2】发奖领域构建、重温简单工厂模式
参考分支 | 210904_xfg_award |
开发分支 | dev_220118_01_award |
1.需求分析
发奖domain领域构建
(截止到目前开发实现的都是关于 domain
领域层的建设,当各项核心的领域服务开发完成以后,则会在 application
层做服务编排流程处理的开发。例如:从用户参与抽奖活动、过滤规则、执行抽奖、存放结果、发送奖品等内容的链路处理
2.项目结构设计
lottery-domain
└── src
└── main
└── java
└── cn.itedus.lottery.domain.award
├── model
│ ├── req
│ ├── res
│ ├── vo
├── repository
│ ├── impl
│ │ └── AwardRepository
│ └── IAwardRepository
└── service
├── factory
│ ├── DistributionGoodsFactory.java
│ └── GoodsConfig.java
└── goods
├── impl
│ ├── CouponGoods.java
│ ├── DescGoods.java
│ ├── PhysicalGoods.java
│ └── RedeemCodeGoods.java
├── DistributionBase.java
└── IDistributionGoodsc.java
关于 award 发奖领域中主要的核心实现在于 service 中的两块功能逻辑实现,分别是:goods 商品处理
、factory 工厂🏭
goods:包装适配各类奖品的发放逻辑,目前的抽奖系统仅是给用户返回一个中奖描述,但在实际的业务场景中涉及调用优惠券、兑换码、物流发货等操作,而这些内容经过封装后就可以在商品类下实现了。
factory:工厂模式通过调用方提供发奖类型,返回对应的发奖服务。通过这样由具体的子类决定返回结果,并做相应的业务处理。从而不至于让领域层包装太多的频繁变化的业务属性,因为如果你的核心功能域是在做业务逻辑封装,就会就会变得非常庞大且混乱。
开发说明
【1】数据库规范调整(双峰转下划线),依据P3C 标准借助 Alibaba Java Coding Guidelines
规范代码编写。此外相应的dao实现(mapper.xml)要进行改造(可借助逆向生成进行改造)
【2】运用简单工厂设计模式,搭建发奖领域服务(定义创建对象的接口,让子类自行决定实例化哪个工厂类)
工厂模式应用
Constant中引入对发奖状态AwardState、奖品类型AwardType的枚举定义
发奖适配策略
抽奖,抽象出配送货物接口,把各类奖品模拟成货物、配送代表着发货,包括虚拟奖品和实物奖品
public interface IDistributionGoods {
/**
* 奖品配送接口,奖品类型(1:文字描述、2:兑换码、3:优惠券、4:实物奖品)
*
* @param req 物品信息
* @return 配送结果
*/
DistributionRes doDistribution(GoodsReq req);
}
实现奖品发送
@Component
public class CouponGoods extends DistributionBase implements IDistributionGoods {
@Override
public DistributionRes doDistribution(GoodsReq req) {
// 模拟调用优惠券发放接口
logger.info("模拟调用优惠券发放接口 uId:{} awardContent:{}", req.getuId(), req.getAwardContent());
// 更新用户领奖结果
super.updateUserAwardState(req.getuId(), req.getOrderId(), req.getAwardId(), Constants.AwardState.SUCCESS.getCode(), Constants.AwardState.SUCCESS.getInfo());
return new DistributionRes(req.getuId(), Constants.AwardState.SUCCESS.getCode(), Constants.AwardState.SUCCESS.getInfo());
}
}
简单工厂配置
工厂配置
public class GoodsConfig {
/** 奖品发放策略组 */
protected static Map<Integer, IDistributionGoods> goodsMap = new ConcurrentHashMap<>();
@Resource
private DescGoods descGoods;
@Resource
private RedeemCodeGoods redeemCodeGoods;
@Resource
private CouponGoods couponGoods;
@Resource
private PhysicalGoods physicalGoods;
// 也可通过构造函数的方式实现
@PostConstruct
public void init() {
// 通过map简化if...else...结构
goodsMap.put(Constants.AwardType.DESC.getCode(), descGoods);
goodsMap.put(Constants.AwardType.RedeemCodeGoods.getCode(), redeemCodeGoods);
goodsMap.put(Constants.AwardType.CouponGoods.getCode(), couponGoods);
goodsMap.put(Constants.AwardType.PhysicalGoods.getCode(), physicalGoods);
}
}
工厂使用
@Service
public class DistributionGoodsFactory extends GoodsConfig {
public IDistributionGoods getDistributionGoodsService(Integer awardType){
return goodsMap.get(awardType);
}
}
测试验证
【1】验证
在lottery-interfaces下进行单元测试
@Resource
private DistributionGoodsFactory distributionGoodsFactory;
// 220118
@Test
public void test_award() {
// 执行抽奖
DrawResult drawResult = drawExec.doDrawExec(new DrawReq("小傅哥", 10001L));
// 判断抽奖结果
Integer drawState = drawResult.getDrawState();
if (Constants.DrawState.FAIL.getCode().equals(drawState)) {
logger.info("未中奖 DrawAwardInfo is null");
return;
}
// 封装发奖参数,orderId:2109313442431 为模拟ID,需要在用户参与领奖活动时生成
DrawAwardInfo drawAwardInfo = drawResult.getDrawAwardInfo();
GoodsReq goodsReq = new GoodsReq(drawResult.getuId(), "2109313442431", drawAwardInfo.getAwardId(), drawAwardInfo.getAwardName(), drawAwardInfo.getAwardContent());
// 根据 awardType 从抽奖工厂中获取对应的发奖服务
IDistributionGoods distributionGoodsService = distributionGoodsFactory.getDistributionGoodsService(drawAwardInfo.getAwardType());
DistributionRes distributionRes = distributionGoodsService.doDistribution(goodsReq);
logger.info("测试结果:{}", JSON.toJSONString(distributionRes));
}
【2】问题说明
在测试的时候会遇到org.apache.ibatis.binding.BindingException: Invalid bound statement (not found),通常是mybatis相关绑定异常,结合内容一步步排查:
(1)检查错误日志信息,查看dao接口和mapper定义是否一致(路径、包名等)
(2)如果代码逻辑没有问题,则问题可能出现在没有正常编译,检查对应target文件(发现部分mapper.xml文件并没有编译生产),重新clean主工程、compile再次检查即可;但需注意一点,如果是执行maven clean后需手动执行maven test
生成相应的test-classes,否则清理后测试会出现Class not found: "cn.itedus.lottery.test.ApiTest"
(3)部分数据库字段调整,涉及到的数据处理相关要进行改造(结合测试日志信息进行调整)
3.扩展项
简单工厂模式避免创建者与具体的产品逻辑耦合、满足单一职责,每一个业务逻辑实现都在所属自己的类中完成、满足开闭原则,无需更改使用调用方就可以在程序中引入新的产品类型。但这样也会带来一些问题,比如有非常多的奖品类型,那么实现的子类会极速扩张,对于这样的场景就需要在引入其他设计手段进行处理,例如抽象通用的发奖子领域,自动化配置奖品发奖