跳至主要內容

luckydraw-ddd 05-简单工厂搭建发奖领域

holic-x...大约 5 分钟项目luckydraw-ddd

领域开发-简单工厂搭建发奖领域

学习目的

【1】数据库规范:数据库整改、dao层调整

【2】发奖领域构建、重温简单工厂模式

参考分支210904_xfg_award
开发分支dev_220118_01_award

1.需求分析

​ 发奖domain领域构建

(截止到目前开发实现的都是关于 domain 领域层的建设,当各项核心的领域服务开发完成以后,则会在 application 层做服务编排流程处理的开发。例如:从用户参与抽奖活动、过滤规则、执行抽奖、存放结果、发送奖品等内容的链路处理

7-01

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:工厂模式通过调用方提供发奖类型,返回对应的发奖服务。通过这样由具体的子类决定返回结果,并做相应的业务处理。从而不至于让领域层包装太多的频繁变化的业务属性,因为如果你的核心功能域是在做业务逻辑封装,就会就会变得非常庞大且混乱。

image-20220119112852091

开发说明

【1】数据库规范调整(双峰转下划线),依据P3C 标准借助 Alibaba Java Coding Guidelines规范代码编写。此外相应的dao实现(mapper.xml)要进行改造(可借助逆向生成进行改造)

image-20220118232809864

【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.扩展项

​ 简单工厂模式避免创建者与具体的产品逻辑耦合、满足单一职责,每一个业务逻辑实现都在所属自己的类中完成、满足开闭原则,无需更改使用调用方就可以在程序中引入新的产品类型。但这样也会带来一些问题,比如有非常多的奖品类型,那么实现的子类会极速扩张,对于这样的场景就需要在引入其他设计手段进行处理,例如抽象通用的发奖子领域,自动化配置奖品发奖

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3