如何设计一个既通用又高效的前置校验?责任链模式来帮忙!

1. 什么是前置校验

前置校验是软件开发中一个重要的环节,它确保数据在进入核心处理逻辑之前满足一定的条件或标准。

2. 举个例子🌰

2.1 下单场景

在电商系统下单接口中,前置校验是非常重要的环节。下面是一个可能的校验步骤列表:

  • 商品信息校验:

    • 检查商品信息是否存在,包括商品名称、价格、规格等信息。
  • 合法性校验:

    • 检查购买数量是否合法,是否超出了最大购买数量或最小购买数量的限制。
    • 检查商品库存是否充足,以确保库存足够满足购买者的需求。
    • 检查购买者的优惠券、积分等是否可以使用,以确保购买者能够享受相应的优惠或积分奖励。
    • 检查收货地址信息是否完整和准确,以确保商品能够顺利地送达给购买者。
    • 检查下单时间是否合法,例如检查购买者是否在限定的时间范围内下单。

2.2 用户注册

  • 用户名校验:检查用户名是否已存在,是否符合命名规则(如长度限制、字符类型限制等)。
  • 密码校验:检查密码长度是否足够,是否包含数字、字母和特殊字符等要求。
  • 邮箱校验:检查邮箱格式是否正确,确保是一个有效的邮箱地址。

对于完成这些前置校验逻辑,如果将代码全写一块的话,常常需要写上几百甚至是上千行代码:

public String createOrder(CreateOrderReqDTO parameter) {
    // 检查商品信息是否存在,包括商品名称、价格、规格等信息
  	// 检查购买数量是否合法,是否超出了最大购买数量或最小购买数量的限制
    // 检查商品库存是否充足,以确保库存足够满足购买者的需求
    // 检查购买者的优惠券、积分等是否可以使用,以确保购买者能够享受相应的优惠或积分奖励
    // 检查收货地址信息是否完整和准确,以确保商品能够顺利地送达给购买者
    // 检查下单时间是否合法,例如检查购买者是否在限定的时间范围内下单
    // ......
}

为了避免这种代码臃肿的情况,我们可以运用责任链设计模式,对前各种置校验逻辑进行抽象。

3. 责任链模式介绍

在责任链模式中,多个处理器(参照上述拦截器)依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条,链条上的每个处理器各自承担各自的处理职责,如下图所示。

在这里插入图片描述

所以在责任链模式很适合前置校验的场景, 当全部校验通过后才执行核心逻辑, 校验不通过则直接返回, 不让请求到达下一个处理器。

4. 责任链模式的经典结构

  1. 处理者 (Handler) 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。
  2. 基础处理者 (Base Handler) 是一个可选的类,你可以将所有处理者共用的样本代码放置在其中。
  3. 具体处理者 (Concrete Handlers) 包含处理请求的实际代码。 每个处理者接收到请求后,都必须决定是否进行处理,以及是否沿着链传递请求。
  4. 客户端 (Client) 可根据程序逻辑一次性或者动态地生成链。

在这里插入图片描述

图片来源: https://refactoringguru.cn/design-patterns/chain-of-responsibility

5. 代码实现

5.1 定义抽象责任链处理接口(公共)

public interface AbstractChainHandler<T> extends Ordered {
    
    /**
     * 执行责任链逻辑
     *
     * @param requestParam 责任链执行入参
     */
    void handler(T requestParam);
    
    /**
     * @return 责任链组件标识
     */
    String mark();
}

mark方法,以便不同业务使用不同的标识。
假设项目中有两个业务场景:订单下单和用户创建都需要责任链模式去验证,mark就是用来进行分组,在业务中进行调用责任链时传递不同的mark方法参数,通过该参数找到对应的一组责任链具体实现类集合。

5.2 定义抽象责任链上下文(公共)

通过实现 CommandLineRunner 接口在 SpringBoot 启动完成后,执行钩子函数将所有实现责任链抽象接口的实现类进行注册到责任链上下文中。

public final class AbstractChainContext<T> implements CommandLineRunner {
    
    private final Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();
    
    /**
     * 责任链组件执行
     *
     * @param requestParam 请求参数
     */
    public void handler(String mark, T requestParam) {
        abstractChainHandlerContainer.get(mark).stream()
            	// 排序后逐个执行
                .sorted(Comparator.comparing(Ordered::getOrder)).forEach(each -> each.handler(requestParam));
    }
    
    @Override
    public void run(String... args) throws Exception {
        // 获取 Spring IOC 容器中所有 AbstractChainHandler 接口实现
        Map<String, AbstractChainHandler> chainFilterMap = ApplicationContextHolder.getBeansOfType(AbstractChainHandler.class);
        chainFilterMap.forEach((beanName, bean) -> {
            List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(bean.mark());
            if (abstractChainHandlers == null) {
                abstractChainHandlers = new ArrayList();
            }
            abstractChainHandlers.add(bean);
            // 根据 mark 标识将责任链模式分类,放入责任链容器上下文中
            abstractChainHandlerContainer.put(bean.mark(), abstractChainHandlers);
        });
    }
}

以上是共用的基础类, 下面以下单的前置校验为例子🌰

5.3 定义抽象业务接口(订单前置校验)

// 订单创建责任链过滤器
public interface OrderCreateChainFilter<T extends OrderCreateCommand> extends AbstractChainHandler<OrderCreateCommand> {
    
    @Override
    default String mark() {
        return "ORDER"; // 标识 (map中的key)
    }
}

5.4 责任链中具体实现(订单前置校验)

分别定义三个 Handler 作用分别为

  • 订单创建参数必填检验
  • 订单创建参数正确性检验
  • 订单创建商品 SKU 库存验证

当所有的作为前置校验Handler 都通过后, 则进行下单的核心逻辑
注: order要有适当间隔, 便于后续添加, 不用全局修改

// 订单创建参数必填检验
@Component
public final class OrderCreateParamNotNullChainHandler implements OrderCreateChainFilter<OrderCreateCommand> {
    
    @Override
    public void handler(OrderCreateCommand requestParam) {
    	if(requestParam.getGoodsId == null || requestParam.getGoodsId == ""){
            throw new ServiceException("参数不能为空");
        }
    }
    
    @Override
    public int getOrder() {
        return 0; // 非空校验 优先级最高 有错误直接拦下 不走下一个实现
    }
}

// 订单创建参数正确性检验
@Component
public final class OrderCreateParamVerificationChainHandler implements OrderCreateChainFilter<OrderCreateCommand> {
    
    @Override
    public void handler(OrderCreateCommand requestParam) {
    	if(requestParam.getGoodsCount >= 100){
            throw new ServiceException("购买数量不能超过100个");
        }
    }
    
    @Override
    public int getOrder() {
        return 5; // order有适当间隔, 便于后续添加, 不用全局修改
    }
}

// 订单创建商品 SKU 库存验证
@Component
public final class OrderCreateProductSkuStockChainHandler implements OrderCreateChainFilter<OrderCreateCommand> {

    @Override
    public void handler(OrderCreateCommand requestParam) {
    	int stock = 10;
        if(requestParam.getGoodsCount >= stock){
            throw new ServiceException("库存不足");
        }
    }
    
    @Override
    public int getOrder() {
        return 10; // order有适当间隔, 便于后续添加, 不用全局修改
    }
}

5.5 业务场景使用(订单前置校验)

@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final AbstractChainContext<OrderCreateCommand> abstractChainContext;
  
    public String createOrder(OrderCreateCommand requestParam) {
        // 责任链模式: 执行订单创建参数验证
        abstractChainContext.handler("ORDER", requestParam); // "ORDER" map中的key
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tiantian17)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值