SpringBoot - 用责任链模式实现业务编排

news2025/5/10 18:45:40

文章目录

  • 前因
  • 责任链:像工作台一样组织代码
  • Code
    • SEQ
    • 3.1 定义处理器规范
    • 3.2 实现具体处理器
    • 3.3 共享上下文
    • 3.4 组装责任链
  • 适用场景
  • 优势

在这里插入图片描述


前因

2000多行的业务逻辑里,各种校验规则、促销计算、库存操作像意大利面条一样缠绕在一起。最要命的是这样的代码结构:

public void createOrder(OrderRequest request) {
    // 参数校验
    if(request.getUserId() == null){
        throw new Exception("用户ID不能为空");
    }
    if(request.getItems().isEmpty()){
        throw new Exception("商品不能为空");
    }
    
    // 库存检查
    for(Item item : request.getItems()){
        if(!inventoryService.checkStock(item)){
            throw new Exception("库存不足");
        }
    }
    
    // 优惠计算
    if(request.getCouponId() != null){
        // 复杂的优惠计算逻辑...
    }
    
    // 风控检查
    if(riskService.checkRisk(request)){
        throw new Exception("风控拦截");
    }
    
    // 保存订单
    // ...
}

每次需求变更都像在雷区跳舞,稍有不慎就会引发连锁反应。新来的小伙伴看着满屏的if-else,战战兢兢地问:“咱们能不能换个写法?”


责任链:像工作台一样组织代码

这时候就该责任链模式登场了!这个设计模式的核心思想是:把每个处理步骤拆成独立的处理器,像流水线一样连接起来

想象一下快递分拣系统:

  1. 包裹先过安检机(校验处理器)
  2. 然后到分拣区(路由处理器)
  3. 接着称重计费(价格处理器)
  4. 最后装车发货(持久化处理器)

每个环节只关心自己的职责,处理完就交给下一个环节。这样无论是要调整环节顺序,还是增加新的处理环节,都变得非常灵活。

Code

SEQ

客户端 OrderController OrderChainManager ValidationHandler StockCheckHandler DiscountHandler OrderContext POST /orders (OrderRequest) 创建OrderContext executeChain(context) 获取所有处理器并排序 handle(context) 参数校验 return false return true alt [校验失败] [校验成功] handle(context) 库存检查 return false return true alt [库存不足] [库存充足] handle(context) 优惠计算 return true 继续后续处理器... alt [库存充足] alt [校验成功] 返回OrderResult 200 OK (OrderResult) 400 BadRequest (ErrorInfo) alt [全部成功] [任何失败] 客户端 OrderController OrderChainManager ValidationHandler StockCheckHandler DiscountHandler OrderContext

3.1 定义处理器规范

package com.artisan.chain.handler;

import com.artisan.chain.model.OrderContext;

/**
 * 处理器接口定义
 */
public interface OrderHandler {

    /**
     * 处理 : 返回true ,继续处理,返回false,终止处理
     *
     * @param orderContext
     * @return
     */
    boolean handle(OrderContext orderContext);

    /**
     * 获取处理顺序
     *
     * @return
     */
    int getOrder();
}

3.2 实现具体处理器

参数校验处理器

 package com.artisan.chain.handler;

import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import com.artisan.chain.model.OrderResult;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

@Component
@Order(100)
public class ValidationHandler implements OrderHandler {
    /**
     * 处理订单请求的函数
     * 该函数主要用于验证订单请求中的必要信息是否完整
     * 如果验证失败,它会设置上下文结果并返回false
     *
     * @param context 订单上下文,包含订单请求和结果
     * @return 如果验证通过,返回true;否则返回false
     */
    @Override
    public boolean handle(OrderContext context) {
        // 获取订单请求
        OrderRequest request = context.getRequest();

        // 验证用户ID是否为空
        if (StringUtils.isEmpty(request.getUserId())) {
            // 如果用户ID为空,设置验证失败的结果并返回
            context.setResult(OrderResult.fail("VALIDATION_ERROR", "用户ID不能为空"));
            return false;
        }

        // 验证订单商品列表是否为空
        if (CollectionUtils.isEmpty(request.getItems())) {
            // 如果订单商品列表为空,设置验证失败的结果并返回
            context.setResult(OrderResult.fail("VALIDATION_ERROR", "订单商品不能为空"));
            return false;
        }

        // 如果所有验证都通过,返回true
        return true;
    }

    /**
     * 获取当前对象的顺序值
     *
     * 顺序值用于确定对象处理的优先级或显示顺序
     * 值越小 优先级越高
     *
     * @return 返回顺序值100,表示当前对象的默认顺序位置
     */
    @Override
    public int getOrder() {
        return 100;
    }
}

库存检查处理器

 package com.artisan.chain.handler;

import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import com.artisan.chain.model.OrderResult;
import com.artisan.chain.service.InventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 库存检查处理器,用于在订单处理流程中检查商品库存
 */
@Component
@Order(200)
public class StockCheckHandler implements OrderHandler{
    // 注入库存服务,用于检查商品库存情况
    @Autowired
    private InventoryService inventoryService;

    /**
     * 处理订单中的库存检查逻辑
     * 遍历订单中的每项商品,检查库存是否充足
     * 如果任何商品的库存不足,订单处理失败,并更新订单上下文的结果
     *
     * @param context 订单上下文,包含订单请求和处理结果
     * @return 如果所有商品库存充足,返回true;否则返回false
     */
    @Override
    public boolean handle(OrderContext context) {
        // 遍历订单中的每个商品项,检查库存
        for (OrderRequest.OrderItem item : context.getRequest().getItems()) {
            // 如果库存检查失败,更新订单上下文的结果为库存错误,并返回false
            if (!inventoryService.checkStock(item.getSkuId(), item.getQuantity())) {
                context.setResult(OrderResult.fail("STOCK_ERROR",
                        "商品[" + item.getSkuId() + "]库存不足"));
                return false;
            }
        }
        // 所有商品库存充足,返回true
        return true;
    }

    /**
     * 获取订单处理的顺序
     * 用于确定在订单处理流程中执行库存检查的顺序
     *
     * @return 订单处理的顺序值
     */
    @Override
    public int getOrder() {
        return 200;
    }
}


package com.artisan.chain.handler;

import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 定义一个处理折扣的组件
 */
@Component
@Order(300)
public class DiscountHandler implements OrderHandler {

    /**
     * 处理订单中的折扣计算
     *
     * @param context 订单上下文,包含订单请求和属性
     * @return 总是返回true,表示处理成功
     */
    @Override
    public boolean handle(OrderContext context) {
        // 模拟优惠计算
        double total = calculateTotal(context.getRequest().getItems());
        double discount = calculateDiscount(total, context.getRequest().getCouponId());

        // 将最终金额存入上下文中
        context.getAttributes().put("finalAmount", total - discount);
        return true;
    }

    /**
     * 计算订单总金额
     *
     * @param items 订单中的商品列表
     * @return 订单总金额
     */
    private double calculateTotal(List<OrderRequest.OrderItem> items) {
        // 模拟价格计算,这里简化处理,实际应根据商品价格和数量计算
        return items.stream()
                .mapToDouble(item -> item.getQuantity() * 100.0) // 模拟价格
                .sum();
    }

    /**
     * 计算折扣金额
     *
     * @param total    订单总金额
     * @param couponId 优惠券ID,如果为空则不应用折扣
     * @return 折扣金额
     */
    private double calculateDiscount(double total, String couponId) {
        // 模拟优惠计算,如果有优惠券ID,则应用10%的折扣
        return couponId != null ? total * 0.1 : 0;
    }

    /**
     * 获取处理顺序
     *
     * @return 处理顺序值
     */
    @Override
    public int getOrder() {
        return 300;
    }
}


3.3 共享上下文

package com.artisan.chain.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.HashMap;
import java.util.Map;

/**
 * 上下文对象(共享数据载体)
 *
 * 订单上下文类,用于处理订单请求并生成订单结果
 * 它封装了订单请求、处理结果以及相关属性
 *
 */

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderContext {

    /**
     * 订单请求对象,包含订单的相关请求信息
     */
    private OrderRequest request;

    /**
     * 订单处理结果对象,用于存储订单处理后的信息
     */
    private OrderResult result = new OrderResult();

    /**
     * 订单相关属性集合,用于存储订单处理过程中需要的临时信息
     * 键为属性名称,值为属性值
     */
    private Map<String, Object> attributes = new HashMap<>();

    /**
     * 构造方法,初始化订单上下文
     *
     * @param request 订单请求对象,不能为空
     */
    public OrderContext(OrderRequest request) {
        this.request = request;
    }
}



3.4 组装责任链

 package com.artisan.chain.manager;


import com.artisan.chain.handler.OrderHandler;
import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderResult;
import org.springframework.stereotype.Component;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 订单链式处理器管理类
 * 通过链式调用多个OrderHandler来处理订单逻辑
 * 该类负责构建和管理这些处理订单的处理器链
 */
@Component
public  class OrderChainManager {

    /**
     * 存储所有的订单处理器,按照处理顺序排序
     */
    private final List<OrderHandler> handlers;

    /**
     * 构造函数,初始化订单处理器链
     * @param handlers 一个未排序的订单处理器集合
     */
    public OrderChainManager(List<OrderHandler> handlers) {
        // 根据每个处理器的顺序值进行排序,确保它们按照正确的顺序执行
        this.handlers = handlers.stream()
                .sorted(Comparator.comparingInt(OrderHandler::getOrder))
                .collect(Collectors.toList());
    }

    /**
     * 执行订单处理逻辑
     * 遍历每个订单处理器,直到所有处理器都处理完毕或某个处理器决定中断链式处理
     * @param context 订单上下文,包含订单的处理信息和结果
     * @return 处理后的订单结果
     */
    public OrderResult execute(OrderContext context) {
        // 遍历处理器链,如果某个处理器处理失败(返回false),则中断链式处理
        for (OrderHandler handler : handlers) {
            if (!handler.handle(context)) {
                break;
            }
        }
        // 返回最终的订单处理结果
        return context.getResult();
    }

}

package com.artisan.chain.config;

import com.artisan.chain.handler.OrderHandler;
import com.artisan.chain.manager.OrderChainManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Collections;
import java.util.List;

/**
 * 配置类,用于定义和配置订单处理链相关的Bean
 */
@Configuration
public class HandlerConfig {

    /**
     * 创建并配置OrderChainManager Bean
     *
     * @param handlersProvider 一个对象提供者,用于提供订单处理器列表如果未找到则提供一个空列表
     * @return 返回一个OrderChainManager实例,用于管理订单处理链
     */
    @Bean
    public OrderChainManager orderChainManager(
            ObjectProvider<List<OrderHandler>> handlersProvider) {
        // 初始化OrderChainManager,使用提供的订单处理器列表,如果没有提供则使用空列表
        return new OrderChainManager(handlersProvider.getIfAvailable(Collections::emptyList));
    }
}


# 测试成功案例
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{
    "userId": "user123",
    "items": [
        {"skuId": "SKU001", "quantity": 2},
        {"skuId": "SKU002", "quantity": 1}
    ],
    "couponId": "COUPON2023"
}'

# 测试库存不足
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{
    "userId": "user123",
    "items": [
        {"skuId": "SKU001", "quantity": 200}
    ]
}'

适用场景

  • 多步骤流程:订单创建、审批流、支付流程等

  • 动态业务:需要频繁调整步骤顺序的业务

  • 复杂校验:多层次、多条件的校验场景

  • 插件式架构:需要动态加载/卸载功能的系统

优势

  • 解耦性:每个处理逻辑独立成Handler,修改单个处理器不影响其他组件
  • 可扩展性:新增业务逻辑只需添加新Handler,无需修改主流程
  • 动态编排:通过配置灵活调整处理器执行顺序和启用状态
  • 可测试性:每个Handler可单独进行单元测试
  • 复用性:通用处理器(如日志记录)可跨多个业务场景复用

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2311451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ResNet 改进:轻量级的混合本地信道注意机制MLCA

目录 1. MLCA注意力机制 2. 改进位置 3. 完整代码 Tips:融入模块后的网络经过测试,可以直接使用,设置好输入和输出的图片维度即可 1. MLCA注意力机制 MLCA(Mixed Local Channel Attention)是一种轻量级的混合本地信道注意机制,旨在提升卷积神经网络(CNN)在图像处理…

【第22节】C++设计模式(行为模式)-Iterator(迭代器)模式

一、问题背景 Iterator 模式是设计模式中最为常见和实用的模式之一。它的核心思想是将对聚合对象的遍历操作封装到一个独立的类中&#xff0c;从而避免暴露聚合对象的内部表示。通过 Iterator 模式&#xff0c;我们可以实现对聚合对象的统一遍历接口&#xff0c;而不需要关心聚…

PyTorch基础语法万字解析

第一章&#xff1a;张量基础&#xff08;Tensor Fundamentals&#xff09; 1.1 张量创建 在PyTorch中&#xff0c;张量&#xff08;Tensor&#xff09;是用于表示数据的基本单元。它类似于NumPy中的数组&#xff0c;但额外支持GPU加速和自动微分功能。以下是几种创建张量的方…

eclipse查看源码

查看 Collection 源码的步骤 打开 Eclipse。 在代码中定位到 Collection 接口&#xff1a; 例如&#xff0c;在代码中输入 Collection&#xff0c;然后按住 Ctrl 键并单击 Collection。 或者直接在代码中使用 Collection 的地方按 F3 键。 如果源码已关联&#xff1a; Ecl…

robot:生而为奴

英文单词 robot&#xff0c;含义是”机器人“。 robot n.机器人 但其实&#xff0c;robot 这个单词的字面义&#xff0c;是生而为奴&#xff1a; robot rob打劫、搜刮 ot &#xff08;天生&#xff09;被剥削者 生而为奴 单词 bot&#xff0c;也指机器人&#xff0c;它是…

操作系统 2.3-用户级线程

多进程的回顾 多进程概念&#xff1a; 操作系统能够同时管理多个进程&#xff08;PID:1, PID:2, PID:3&#xff09;&#xff0c;每个进程可以独立执行一系列指令。 进程结构&#xff1a; 每个进程拥有自己的代码段、数据段、堆和栈。 进程控制块&#xff08;PCB&#xff09;…

解决火绒启动时,报安全服务异常,无法保障计算机安全

1.找到控制面板-安全和维护-更改用户账户控制设置 重启启动电脑解决。

小程序事件系统 —— 32 事件系统 - 事件分类以及阻止事件冒泡

在微信小程序中&#xff0c;事件分为 冒泡事件 和 非冒泡事件 &#xff1a; 冒泡事件&#xff1a;当一个组件的事件被触发后&#xff0c;该事件会向父节点传递&#xff1b;&#xff08;如果父节点中也绑定了一个事件&#xff0c;父节点事件也会被触发&#xff0c;也就是说子组…

STM32点亮LED灯

1.1 介绍&#xff1a; LED模块。它的控制方法非常简单&#xff0c;要想点亮LED&#xff0c;只要让它两端有一定的电压就可以&#xff1b;实验中&#xff0c;我们通过编程控制信号端S的高低电平&#xff0c;从而控制LED的亮灭。我们提供一个测试代码控制LED模块上实现闪烁的效果…

C++ primer plus 第七节 函数探幽完结版

系列文章目录 C primer plus 第一节 步入C-CSDN博客 C primer plus 第二节 hello world刨析-CSDN博客 C primer plus 第三节 数据处理-CSDN博客 C primer plus 第四节 复合类型-CSDN博客 C primer plus 第五节 循环-CSDN博客 C primier plus 第七节 函数探幽第一部分-CSDN博客 …

打破界限!家电行业3D数字化营销,线上线下无缝对接

家电行业正步入从增量市场向存量市场的转型期&#xff0c;消费者的观念日益成熟&#xff0c;对产品体验和服务质量的要求愈发严格。无论是线上电商平台还是线下实体店铺&#xff0c;提供个性化、增强体验感的产品与服务已成为家电市场未来发展的核心动力。51建模网凭借“3D数字…

13 【HarmonyOS NEXT】 仿uv-ui组件开发之Avatar组件进阶指南(四)

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 文章目录 补充内容第四篇&#xff1a;打造高性能Avatar组件的终极优化秘籍1. 性能优化策略1.1 状态管理优化1.2 渲染性能优化 2. 资源优化2.1 图片…

[Vue warn]: Duplicate keys detected: ‘xxx‘. This may cause an update error.

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…

设计模式 - 工厂模式 精准梳理精准记忆

1、代码片段 - 带入理解 一、核心模式分类 简单工厂模式&#xff08;编程习惯&#xff0c;非 GoF 设计模式&#xff09;工厂方法模式&#xff08;GoF 创建型模式&#xff09;抽象工厂模式&#xff08;GoF 创建型模式&#xff09; 二、演变过程&#xff1a;咖啡店案例 初始实现…

NVIDIA(英伟达) GPU 芯片架构发展史

GPU 性能的关键参数 CUDA 核心数量&#xff08;个&#xff09;&#xff1a;决定了 GPU 并行处理能力&#xff0c;在 AI 等并行计算类业务下&#xff0c;CUDA 核心越多性能越好。 显存容量&#xff08;GB&#xff09;&#xff1a;决定了 GPU 加载数据量的大小&#xff0c;在 AI…

springboot项目使用中创InforSuiteAS替换tomcat

springboot项目使用中创InforSuiteAS替换tomcat 学习地址一、部署InforSuiteAS1、部署2、运行 二、springboot项目打包成war包 特殊处理1、pom文件处理1、排除内嵌的tomcat包2、新增tomcat、javax.servlet-api3、打包格式设置为war4、打包后的项目名称5、启动类修改1、原来的不…

Tomcat-web服务器介绍以及安装部署

一、Tomcat简介 Tomcat是Apache软件基金会&#xff08;Apache Software Foundation&#xff09;的Jakarta 项目中的一个核心项目&#xff0c;由Apache、Sun和其他一些公司及个人共同开发而成。 Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用…

18类创新平台培育入库!长沙经开区2025年各类科技创新平台培育申报流程时间材料及申报条件

长沙经开区打算申报企业研发中心、技术创新中心、工程技术研究中心、新型研发机构、重点实验室、概念验证中心和中试平台、工程研究中心、企业技术中心、制造业创新中心、工业设计中心等创新平台的可先备案培育入库&#xff0c;2025年各类平台的认定将从培育库中优先推荐&#…

使用 Elasticsearch 进行集成测试初始化​​数据时的注意事项

作者&#xff1a;来自 Elastic piotrprz 在创建应该使用 Elasticsearch 进行搜索、数据聚合或 BM25/vector/search 的软件时&#xff0c;创建至少少量的集成测试至关重要。虽然 “模拟索引” 看起来很诱人&#xff0c;因为测试甚至可以在几分之一秒内运行&#xff0c;但它们实际…

9. Flink的性能优化

1. Flink的资源和代码优化 1.1 slot资源配置 Flink中具体跑任务的进程叫TaskManager&#xff0c;TM进程又会根据配置划分出诺干个TaskSlot&#xff0c;它是具体运行SubTask的地方。slot是Flink用来隔离各个subtask的资源集合&#xff0c;这里的资源一把指内存&#xff0c;TCP…