Pipeline mode application | JD Cloud technical team

This article records the application of Pipeline design pattern in business process orchestration

Preface

Pipeline mode means pipeline mode, also known as pipeline mode. It is designed to process input data through a predetermined series of stages, and the output of each stage is the input of the next stage.

In this case, by defining PipelineProduct (pipeline product), PipelineJob (pipeline task), and PipelineNode (pipeline node), the assembly of an entire pipeline is completed, and "raw materials" are processed into "commodities". Among them, pipeline products are responsible for carrying product information at each stage; pipeline tasks are responsible for processing products at different stages; pipeline nodes constrain the relationship between pipeline products and tasks, and define the execution method of tasks through semaphores.

rely

Tool dependencies are as follows

            <!-- 工具类大全 -->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>最新版本</version>
            </dependency>



Programming examples

1. Pipe product definition

package com.example.demo.pipeline.model;

/**
 * 管道产品接口
 *
 * @param <S> 信号量
 * @author 
 * @date 2023/05/15 11:49
 */
public interface PipelineProduct<S> {
}




2. Pipeline task definition

package com.example.demo.pipeline.model;

/**
 * 管道任务接口
 *
 * @param <P> 管道产品
 * @author 
 * @date 2023/05/15 11:52
 */
@FunctionalInterface
public interface PipelineJob<P> {
    /**
     * 执行任务
     *
     * @param product 管道产品
     * @return {@link P}
     */
    P execute(P product);
}



3. Pipeline node definition

package com.jd.baoxian.mall.market.service.pipeline.model;

import java.util.function.Predicate;

/**
 * 管道节点定义
 *
 * @param <S> 信号量
 * @param <P> 管道产品
 * @author 
 * @date 2023/05/15 11:54
 */
public interface PipelineNode<S, P extends PipelineProduct<S>> {
    /**
     * 节点组装,按照上个管道任务传递的信号,执行 pipelineJob
     *
     * @param pipelineJob 管道任务
     * @return {@link PipelineNode}<{@link S},  {@link P}>
     */
    PipelineNode<S, P> flax(PipelineJob<P> pipelineJob);

    /**
     * 节点组装,按照传递的信号,判断当前管道的信号是否相等,执行 pipelineJob
     *
     * @param signal      信号
     * @param pipelineJob 管道任务
     * @return {@link PipelineNode}<{@link S},  {@link P}>
     */
    PipelineNode<S, P> flax(S signal, PipelineJob<P> pipelineJob);

    /**
     * 节点组装,按照传递的信号,判断当前管道的信号是否相等,执行 pipelineJob
     *
     * @param predicate   信号
     * @param pipelineJob 管道任务
     * @return {@link PipelineNode}<{@link S},  {@link P}>
     */
    PipelineNode<S, P> flax(Predicate<S> predicate, PipelineJob<P> pipelineJob);

    /**
     * 管道节点-任务执行
     *
     * @param product 管道产品
     * @return {@link P}
     */
    P execute(P product);
}




4. Implementation of pipeline products, tasks, and nodes

4.1 Pipe products

package com.example.demo.pipeline.factory;


import com.example.demo.model.request.DemoReq;
import com.example.demo.model.response.DemoResp;
import com.example.demo.pipeline.model.PipelineProduct;
import lombok.*;

/**
 * 样例-管道产品
 *
 * @author 
 * @date 2023/05/15 14:04
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DemoPipelineProduct implements PipelineProduct<DemoPipelineProduct.DemoSignalEnum> {
    /**
     * 信号量
     */
    private DemoSignalEnum signal;

    /**
     * 产品-入参及回参
     */
    private DemoProductData productData;

    /**
     * 异常信息
     */
    private Exception exception;

    /**
     * 流程Id
     */
    private String tradeId;

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class DemoProductData {
        /**
         * 待验证入参
         */
        private DemoReq userRequestData;

        /**
         * 待验证回参
         */
        private DemoResp userResponseData;
    }

    /**
     * 产品-信号量
     *
     * @author 
     * @date 2023/05/15 13:54
     */
    @Getter
    public enum DemoSignalEnum {
        /**
         *
         */
        NORMAL(0, "正常"),
        /**
         *
         */
        CHECK_NOT_PASS(1, "校验不通过"),
        /**
         *
         */
        BUSINESS_ERROR(2, "业务异常"),
        /**
         *
         */
        LOCK_ERROR(3, "锁处理异常"),
        /**
         *
         */
        DB_ERROR(4, "事务处理异常"),

        ;
        /**
         * 枚举码值
         */
        private final int code;
        /**
         * 枚举描述
         */
        private final String desc;

        /**
         * 构造器
         *
         * @param code
         * @param desc
         */
        DemoSignalEnum(int code, String desc) {
            this.code = code;
            this.desc = desc;
        }
    }
}





4.2 Pipeline tasks (abstract class)

package com.example.demo.pipeline.factory.job;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.json.JSONUtil;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import com.example.demo.pipeline.model.PipelineJob;
import lombok.extern.slf4j.Slf4j;

/**
 * 管道任务-抽象层
 *
 * @author 
 * @date 2023/05/15 19:48
 */
@Slf4j
public abstract class AbstractDemoJob implements PipelineJob<DemoPipelineProduct> {

    /**
     * 公共执行逻辑
     *
     * @param product 产品
     * @return
     */
    @Override
    public DemoPipelineProduct execute(DemoPipelineProduct product) {
        DemoPipelineProduct.DemoSignalEnum newSignal;
        try {
            newSignal = execute(product.getTradeId(), product.getProductData());
        } catch (Exception e) {
            product.setException(e);
            newSignal = DemoPipelineProduct.DemoSignalEnum.BUSINESS_ERROR;
        }
        product.setSignal(newSignal);
        defaultLogPrint(product.getTradeId(), product);
        return product;
    }

    /**
     * 子类执行逻辑
     *
     * @param tradeId     流程Id
     * @param productData 请求数据
     * @return
     * @throws Exception 异常
     */
    abstract DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) throws Exception;

    /**
     * 默认的日志打印
     */
    public void defaultLogPrint(String tradeId, DemoPipelineProduct product) {
        if (!DemoPipelineProduct.DemoSignalEnum.NORMAL.equals(product.getSignal())) {
            log.info("流水线任务处理异常:流程Id=【{}】,信号量=【{}】,任务=【{}】,参数=【{}】", tradeId, product.getSignal(),
                    ClassUtil.getClassName(this, true), JSONUtil.toJsonStr(product.getProductData()), product.getException());
        }
    }

}




4.3 Pipeline nodes

package com.example.demo.pipeline.factory;


import cn.hutool.core.util.ClassUtil;
import cn.hutool.json.JSONUtil;
import com.example.demo.pipeline.model.PipelineJob;
import com.example.demo.pipeline.model.PipelineNode;
import lombok.extern.slf4j.Slf4j;

import java.util.function.Predicate;

/**
 * 审核-管道节点
 *
 * @author 
 * @date 2023/05/15 14:32
 */
@Slf4j
public class DemoPipelineNode implements PipelineNode<DemoPipelineProduct.DemoSignalEnum, DemoPipelineProduct> {

    /**
     * 下一管道节点
     */
    private DemoPipelineNode next;

    /**
     * 当前管道任务
     */
    private PipelineJob<DemoPipelineProduct> job;

    /**
     * 节点组装,按照上个管道任务传递的信号,执行 pipelineJob
     *
     * @param pipelineJob 管道任务
     * @return {@link DemoPipelineNode}
     */
    @Override
    public DemoPipelineNode flax(PipelineJob<DemoPipelineProduct> pipelineJob) {
        return flax(DemoPipelineProduct.DemoSignalEnum.NORMAL, pipelineJob);
    }

    /**
     * 节点组装,按照传递的信号,判断当前管道的信号是否相等,执行 pipelineJob
     *
     * @param signal      信号
     * @param pipelineJob 管道任务
     * @return {@link DemoPipelineNode}
     */
    @Override
    public DemoPipelineNode flax(DemoPipelineProduct.DemoSignalEnum signal, PipelineJob<DemoPipelineProduct> pipelineJob) {
        return flax(signal::equals, pipelineJob);
    }

    /**
     * 节点组装,上个管道过来的信号运行 predicate 后是true的话,执行 pipelineJob
     *
     * @param predicate
     * @param pipelineJob
     * @return
     */
    @Override
    public DemoPipelineNode flax(Predicate<DemoPipelineProduct.DemoSignalEnum> predicate,
                                 PipelineJob<DemoPipelineProduct> pipelineJob) {
        this.next = new DemoPipelineNode();
        this.job = (job) -> {
            if (predicate.test(job.getSignal())) {
                return pipelineJob.execute(job);
            } else {
                return job;
            }
        };
        return next;
    }

    /**
     * 管道节点-任务执行
     *
     * @param product 管道产品
     * @return
     */
    @Override
    public DemoPipelineProduct execute(DemoPipelineProduct product) {
        // 执行当前任务
        try {
            product = job == null ? product : job.execute(product);
            return next == null ? product : next.execute(product);
        } catch (Exception e) {
            log.error("流水线处理异常:流程Id=【{}】,任务=【{}】,参数=【{}】", product.getTradeId(), ClassUtil.getClassName(job, true), JSONUtil.toJsonStr(product.getProductData()), product.getException());
            return null;
        }

    }
}





5. Business realization

Through the previous definition, we can already complete the construction of the pipeline through Pipeline, and then complete the application using the business scenario of "review information submission".

5.1 Define Api, input parameters, and return parameters

package com.example.demo.api;

import com.example.demo.model.request.DemoReq;
import com.example.demo.model.response.DemoResp;
import com.example.demo.pipeline.factory.PipelineForManagerSubmit;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * 演示-API
 *
 * @author 
 * @date 2023/08/06 16:27
 */
@Service
public class DemoManagerApi {

    /**
     * 管道-审核提交
     */
    @Resource
    private PipelineForManagerSubmit pipelineForManagerSubmit;

    /**
     * 审核提交
     *
     * @param requestData 请求数据
     * @return {@link DemoResp}
     */
    public DemoResp managerSubmit(DemoReq requestData) {
        return pipelineForManagerSubmit.managerSubmitCheck(requestData);
    }
}


package com.example.demo.model.request;

/**
 * 演示入参
 *
 * @author 
 * @date 2023/08/06 16:33
 */
public class DemoReq {
}


package com.example.demo.model.response;

import lombok.Data;

/**
 * 演示回参
 *
 * @author 
 * @date 2023/08/06 16:33
 */
@Data
public class DemoResp {
    /**
     * 成功标识
     */
    private Boolean success = false;

    /**
     * 结果信息
     */
    private String resultMsg;

    /**
     * 构造方法
     *
     * @param message 消息
     * @return {@link DemoResp}
     */
    public static DemoResp buildRes(String message) {
        DemoResp response = new DemoResp();
        response.setResultMsg(message);
        return response;
    }
}





5.2 Define specific tasks

It is assumed that the review submission process needs to include: parameter verification, locking, unlocking, and transaction submission

package com.example.demo.pipeline.factory.job;

import cn.hutool.json.JSONUtil;
import com.example.demo.model.request.DemoReq;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 加锁-实现层
 *
 * @author 
 * @date 2023/05/17 17:00
 */
@Service
@Slf4j
public class CheckRequestLockJob extends AbstractDemoJob {

    /**
     * 子类执行逻辑
     *
     * @param tradeId     流程Id
     * @param productData 请求数据
     * @return
     * @throws Exception 异常
     */
    @Override
    DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) throws Exception {
        DemoReq userRequestData = productData.getUserRequestData();
        log.info("任务[{}]加锁,线程号:{}", JSONUtil.toJsonStr(userRequestData), tradeId);
        return DemoPipelineProduct.DemoSignalEnum.NORMAL;
    }
}


package com.example.demo.pipeline.factory.job;

import cn.hutool.json.JSONUtil;
import com.example.demo.model.request.DemoReq;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 解锁-实现层
 *
 * @author 
 * @date 2023/05/17 17:00
 */
@Service
@Slf4j
public class CheckRequestUnLockJob extends AbstractDemoJob {

    /**
     * 子类执行逻辑
     *
     * @param tradeId     流程Id
     * @param productData 请求数据
     * @return
     * @throws Exception 异常
     */
    @Override
    DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) throws Exception {
        DemoReq userRequestData = productData.getUserRequestData();
        log.info("任务[{}]解锁,线程号:{}", JSONUtil.toJsonStr(userRequestData), tradeId);
        return DemoPipelineProduct.DemoSignalEnum.NORMAL;
    }
}

package com.example.demo.pipeline.factory.job;

import cn.hutool.json.JSONUtil;
import com.example.demo.model.request.DemoReq;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;


/**
 * 审核-参数验证-实现类
 *
 * @author 
 * @date 2023/05/15 19:50
 */
@Slf4j
@Component
public class ManagerCheckParamJob extends AbstractDemoJob {

    /**
     * 执行基本入参验证
     *
     * @param tradeId
     * @param productData 请求数据
     * @return
     */
    @Override
    DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) {
        /*
         * 入参验证
         */
        DemoReq userRequestData = productData.getUserRequestData();
        log.info("任务[{}]入参验证,线程号:{}", JSONUtil.toJsonStr(userRequestData), tradeId);
        // 非空验证

        // 有效验证

        // 校验通过,退出
        return DemoPipelineProduct.DemoSignalEnum.NORMAL;
    }

}


package com.example.demo.pipeline.factory.job;

import cn.hutool.json.JSONUtil;
import com.example.demo.model.request.DemoReq;
import com.example.demo.model.response.DemoResp;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 审核-信息提交-业务实现
 *
 * @author 
 * @date 2023/05/12 14:36
 */
@Service
@Slf4j
public class ManagerSubmitJob extends AbstractDemoJob {

    /**
     * 子类执行逻辑
     *
     * @param tradeId     流程Id
     * @param productData 请求数据
     * @return
     * @throws Exception 异常
     */
    @Override
    DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) throws Exception {
        DemoReq userRequestData = productData.getUserRequestData();
        try {
            /*
             * DB操作
             */
            log.info("任务[{}]信息提交,线程号:{}", JSONUtil.toJsonStr(userRequestData), tradeId);
            productData.setUserResponseData(DemoResp.buildRes("成功"));
        } catch (Exception ex) {
            log.error("审核-信息提交-DB操作失败,入参:{}", JSONUtil.toJsonStr(userRequestData), ex);
            throw ex;
        }
        return DemoPipelineProduct.DemoSignalEnum.NORMAL;
    }
}





5.3 Complete assembly line assembly

For input parameter conversion, pipeline task execution sequence and execution semaphore construction

package com.example.demo.pipeline.factory;

import com.example.demo.model.request.DemoReq;
import com.example.demo.model.response.DemoResp;
import com.example.demo.pipeline.factory.job.CheckRequestLockJob;
import com.example.demo.pipeline.factory.job.CheckRequestUnLockJob;
import com.example.demo.pipeline.factory.job.ManagerCheckParamJob;
import com.example.demo.pipeline.factory.job.ManagerSubmitJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Objects;
import java.util.UUID;

/**
 * 管道工厂入口-审核流水线
 *
 * @author 
 * @date 2023/05/15 19:52
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PipelineForManagerSubmit {

    /**
     * 审核-管道节点
     */
    private final DemoPipelineNode managerSubmitNode = new DemoPipelineNode();


    /**
     * 审核-管道任务-提交-防刷锁-加锁
     */
    private final CheckRequestLockJob checkRequestLockJob;

    /**
     * 审核-管道任务-提交-防刷锁-解锁
     */
    private final CheckRequestUnLockJob checkRequestUnLockJob;

    /**
     * 审核-管道任务-参数验证
     */
    private final ManagerCheckParamJob managerCheckParamJob;

    /**
     * 审核-管道任务-事务操作
     */
    private final ManagerSubmitJob managerSubmitJob;


    /**
     * 组装审核的处理链
     */
    @PostConstruct
    private void assembly() {
        assemblyManagerSubmit();
    }

    /**
     * 组装处理链
     */
    private void assemblyManagerSubmit() {

        managerSubmitNode
                // 参数验证及填充
                .flax(managerCheckParamJob)
                // 防刷锁
                .flax(checkRequestLockJob)
                // 事务操作
                .flax(managerSubmitJob)
                // 锁释放
                .flax((ignore) -> true, checkRequestUnLockJob);
    }

    /**
     * 审核-提交处理
     *
     * @param requestData 入参
     * @return
     */
    public DemoResp managerSubmitCheck(DemoReq requestData) {
        DemoPipelineProduct initialProduct = managerSubmitCheckInitial(requestData);
        DemoPipelineProduct finalProduct = managerSubmitNode.execute(initialProduct);
        if (Objects.isNull(finalProduct) || Objects.nonNull(finalProduct.getException())) {
            return DemoResp.buildRes("未知异常");
        }
        return finalProduct.getProductData().getUserResponseData();
    }

    /**
     * 审核-初始化申请的流水线数据
     *
     * @param requestData 入参
     * @return 初始的流水线数据
     */
    private DemoPipelineProduct managerSubmitCheckInitial(DemoReq requestData) {
        // 初始化
        return DemoPipelineProduct.builder()
                .signal(DemoPipelineProduct.DemoSignalEnum.NORMAL)
                .tradeId(UUID.randomUUID().toString())
                .productData(DemoPipelineProduct.DemoProductData.builder().userRequestData(requestData).build())
                .build();
    }
}




Summarize

This article focuses on the abstraction and application of the pipeline pattern. The above examples are for personal understanding only. In practical applications, this case is good at dealing with various business scenarios with complex rules and facilitates rule arrangement.
Points for improvement:

  1. Each task actually implies the order of execution, which can be further implemented;

  2. For the final "pipeline assembly" step, it can be further abstracted through configuration description, so that changes can be controlled in the description of each "pipeline task", and rule items can be processed in a "pluggable" manner.

Author: JD Insurance Hou Yadong

Source: JD Cloud Developer Community Please indicate the source when reprinting

IntelliJ IDEA 2023.3 & JetBrains Family Bucket annual major version update new concept "defensive programming": make yourself a stable job GitHub.com runs more than 1,200 MySQL hosts, how to seamlessly upgrade to 8.0? Stephen Chow's Web3 team will launch an independent App next month. Will Firefox be eliminated? Visual Studio Code 1.85 released, floating window Yu Chengdong: Huawei will launch disruptive products next year and rewrite the history of the industry. The US CISA recommends abandoning C/C++ to eliminate memory security vulnerabilities. TIOBE December: C# is expected to become the programming language of the year. A paper written by Lei Jun 30 years ago : "Principle and Design of Computer Virus Determination Expert System"
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10320022