某家餐饮科技企业文档整理

订单重构新老接口切换方案

订单新老接口切换方案

1. 背景

订单重构代码,防止新代码上线,出现未知bug时控制影响范围,需对请求进行ab分流。

2.技术方案

基于RequestConditionAviator搭配Apollo,对相同url请求,根据参数和配置进行版本判定,将部分请求走新接口,其余请求走旧版本。

如下列配置:

api.version.expressions.POST_order_v2_create="(partnerId != null && partnerId == '2221') ? 2 : 1" 

则代表根据body中的partnerId==2221时,走版本2,其余走版本1

2.1 RequestCondition

用于Spring原生接口,用于处理请求的匹配规则,url header 等匹配本质上都是作为 RequestCondition,只不过Spring已经为我们实现

2.1 Aviator

由于需要根据参数进行动态的版本判定,调研后选择使用Aviator规则引擎,结合Apollo 动态配置来实现。Aviator具有体积小巧,执行速度快(因为会将规则编译为字节码)的优点。

订单重构方案

转至元数据结尾

转至元数据起始

1. 背景

订单基础服务目前存在多个许多针对已迁走商户的定制化业务;接口传递参数不规范;缓存混乱,对一致性未做出保证;代码分层不明确;同层级之间互相循环调用;数据库事务不规范,导致提前发送mq、刷新缓存。 针对以上问题,故进行代码重构,明确代码分层结构,和相关规范。

2. 重构方案

结合CQRS模式和阿里COLA进行分包

2.1. 代码结构

- client 模块
  L api                 // CommandService、QueryService
  L model
    L req               // 命令参数
      L query           // 查询参数
    L resp

- app 模块                   
  L application
    L config            // 配置相关
    L internal
      L commandservice  // CommandServiceImpl
      L queryservice    // QueryServiceImpl
      L outboundservice // ApplicationEventPublisher事件监听
    L excutor           // 执行器
      L query           // 查询执行器
  L interfaces          // 服务入口(web、mq等)
    L controller        // 对外提供web接口
    L scheduled         // 定时任务
    L consumer          // mq消费

- domain模块
  L domain
    L modle               // 各种内部使用实体
      L dto               // 各种内部使用DTO
        L data            // 数据类DTO
      L enums             // 枚举
      L entity            // 数据库映射实体
    L exception           // 异常
    L domainservice       // 公用业务实现
    L gateway             // 内部网关声明,用于屏蔽底层
    L util                // 工具类相关

- infrastructure 模块
  L infrastructure   
    L gateway             // 内部网关实现,调用具体的mq、rpc、mappr、cache           
    L mq                  // mq发送
    L mapper              // 数据库
    L cache               // 缓存
    L rpc                 // 服务调用
      L req
      L resp
      L convert


模块依赖关系:

         app
       /      \
    client    domain
                 \
            infrastructure

订单三个服务,domain和infrastructure进行公用

2.2. 规范

2.2.1. 调用规范

调用层级图:

              controller        
             /         \
    commandservice    queryservice     
             \         /
               excutor
                /   \
               /  domainService
              /    /     
            gateway      
            /    \     
    -------------------
    mapper mq cache rpc
  1. interfaces 只作为入口,不处理具体逻辑,不得直接调用domainService层和infrastructure层。
  2. commandService 和 queryService 中不进行具体的业务处理,而是交由 excutor 进行处理。
  3. excutor 只暴漏一个 execute(cmd) 方法, 事务包裹整个execute 方法。
  4. 具体业务逻辑由 excutor 处理,禁止同层调用,如有公用逻辑,则应当抽取公共部分放入 domainService
  5. 无特殊情况, 在domainService 和 excutor中,尽可能调用gateway层,而非直接访问 infrastructure 。

2.2.2. 命名规范

  1. 命令参数:*Cmd
  2. 查询参数:*Qry
  3. 响应参数:*Resp
  4. 命令执行器:*CmdExe
  5. 查询执行器:*QryExe
  6. DTO:*DTO
  7. 工具类:*Util
  8. 转换类:*Converter
  9. 枚举:*Enum
  10. 缓存管理器:*CacheManager

2.2.3. 领域术语规范

由于订单做为基础服务,针对场景不单只有点餐,所以对于订单状态和订单动作通用术语作出如下调整:


    创建订单        支付成功          接单          备货完成         发货            完成
无  ----->  待支付  ----->  待接单  ----->  备货中  ----->  待交付  ----->  配送中  ----->  已完成                     已取消
              |             \_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _/                         / \
              |                                    |                      / \                                      |
              |                                    |                       |  部分退(商品/金额)   |          全退    |
              |                                    |                        \_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
              |                                   \ /                                            |                 |
              |                                  申请售后        同意申请        退货完成        退款完成                |
              | 取消支付                       无  ----->  待审核  ----->  待退货  ----->  已退货  ----->  已完成 <----\ |
              |                                           /  \                                                    \|
              |                           已拒绝  <-----  /    \  ----->  已取消                                     |
              |                                  拒绝申请        取消申请                                             |
              |                                                                                                    |
               \_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /


2.3. 代码部分

2.3.1. 基础命令执行器模版

命令执行器作为业务处理的核心,已进行高度抽象形成模版,形成统一流程,其他命令执行器,只需要实现各自业务即可。


--- BaseCmdExeTemplate ---

@RequiredArgsConstructor
@Component
public abstract class BaseCmdExeTemplate<C extends CommonCmd, D, E, R> {

    protected final ApplicationEventPublisher applicationEventPublisher;

    @Transactional(rollbackFor = Exception.class)
    public R execute(C cmd) {
        E entity = null;
        try {
            // 验证参数
            checkCmd(cmd);

            // 查询
            entity = queryEntity(cmd);

            // 验证
            checkEntity(cmd, entity);

            // 构造数据
            D saveData = buildSaveBean(cmd, entity);

            // 保存
            save(saveData, entity);

            // 广播事件
            publishEvent(cmd, saveData, entity);

            return buildResp(saveData, entity);
        } catch (Exception e) {
            return processException(cmd, entity, e);
        }
    }
    protected void checkCmd(C cmd) {}

    protected abstract E queryEntity(C cmd);

    protected void checkEntity(C cmd, E entity) {}

    protected abstract D buildSaveBean(C cmd, E entity);

    protected abstract void save(D saveData, E entity);

    protected void publishEvent(C cmd, D saveData, E entity){}

    protected R buildResp(D saveData, E entity) {
        return null;
    }

    protected R processException(C cmd, E entity, Exception exception) {
        if (exception instanceof RuntimeException) {
            throw (RuntimeException) exception;
        }
        throw new RuntimeException(exception);
    }

}

--- OrderBaseCmdExeTemplate ---

@Component
public abstract class OrderBaseCmdExeTemplate<C extends CommonOrderCmd, D, R> extends BaseCmdExeTemplate<C, D, OrderDTO, R> {


    protected final OrderGateway orderGateway;

    public OrderBaseCmdExeTemplate(ApplicationEventPublisher applicationEventPublisher,
                                   OrderGateway orderGateway) {
        super(applicationEventPublisher);
        this.orderGateway = orderGateway;
    }


    @Override
    protected OrderDTO queryEntity(CommonOrderCmd cmd) {
        return orderGateway.queryAssertNotNullOrderByOrderCode(cmd.getPartnerId(), cmd.getOrderCode(),
                QueryFieldEnum.ORDER_INFO
        );
    }
}

--- OrderStateFireCmdExeTemplate ---

@Component
public abstract class OrderStateFireCmdExeTemplate<C extends CommonOrderStateCmd, D, R> extends OrderBaseCmdExeTemplate<C, D, R> {
    protected final StateMachineFactory stateMachineFactory;

    public OrderStateFireCmdExeTemplate(ApplicationEventPublisher applicationEventPublisher,
                                        OrderGateway orderGateway,
                                        StateMachineFactory stateMachineFactory) {
        super(applicationEventPublisher, orderGateway);
        this.stateMachineFactory = stateMachineFactory;
    }

    @Override
    protected void checkEntity(C cmd, OrderDTO entity) {
        // 状态机
        OrderStatus orderState = stateMachineFire(cmd, entity);

        cmd.setFireOrderState(orderState.getCode());
    }

    protected abstract OrderStatus stateMachineFire(C cmd, OrderDTO order);
}


示例: 订单接单


--- OrderController ---

    @PostMapping("/accept")
    public CommonResp<Void> accept(@Validated @RequestBody OrderAcceptCmd cmd) {
        return orderStateCmdService.accept(cmd);
    }

--- OrderStateCmdServiceImpl ---

    @Override
    public CommonResp<Void> accept(OrderAcceptCmd cmd) {
        acceptCmdExe.execute(cmd);
        return CommonResp.success();
    }

--- OrderAcceptCmdExe ---

@Component
public class OrderAcceptCmdExe extends OrderStateFireCmdExeTemplate<OrderAcceptCmd, OrderAcceptDTO, Void> {

    private final OrderTaskGateway orderTaskGateway;

    public OrderAcceptCmdExe(ApplicationEventPublisher applicationEventPublisher,
                             OrderGateway orderGateway,
                             StateMachineFactory stateMachineFactory,
                             OrderTaskGateway orderTaskGateway) {
        super(applicationEventPublisher, orderMag, stateMachineFactory);
        this.orderTaskGateway = orderTaskGateway;
    }

    @Override
    protected OrderStatus stateMachineFire(OrderAcceptCmd cmd, OrderDTO order) {
        OrderStateMachine orderStateMachine = stateMachineFactory.getOrderMachine(order.getOrderState());
        orderStateMachine.fire(OrderStatusEvent.ACCEPT, order);
        return orderStateMachine.getCurrentState();
    }


    @Override
    protected OrderAcceptDTO buildSaveBean(OrderAcceptCmd cmd, OrderDTO order) {
        OrderAcceptDTO acceptData = BeanMapperUtil.map(cmd, OrderAcceptDTO.class);

        acceptData.setOrderState(cmd.getFireOrderState());
        acceptData.setAcceptTime(new Date());
        Map<String, Object> extInfo = new HashMap<>();
        extInfo.put("dispatchType", Optional.ofNullable(cmd.getDispatchType()).orElse("0"));
        extInfo.put("dispatchTimeout", Optional.ofNullable(cmd.getDispatchTimeout()).orElse(0));
        acceptData.setExtInfo(ExtInfoUtil.mergeToStr(order.getExtInfo(), extInfo));

        return acceptData;
    }

    @Override
    protected void save(OrderAcceptDTO saveData, OrderDTO order) {
        orderGateway.accept(saveData, order);

        // 解锁任务
        orderTaskGateway.unBlock(order.getOrderCode(), order.getOrderState(), OrderTaskType.BOOK_TASK.getCode(), saveData.getTimeout());

        if (Objects.equals(OrderClientType.SAAS_MALL.getCode(), order.getOrderClient())
                || Objects.equals(OrderBizType.SAAS_MALL.getCode(), order.getBizType())) {
            // 创建任务
            orderTaskGateway.createOrderProducedRefundTimeout(order.getPartnerId(), order.getOrderCode());
        }
    }

    @Override
    protected void publishEvent(OrderAcceptCmd cmd, OrderAcceptDTO saveData, OrderDTO order) {
        applicationEventPublisher.publishEvent(new OrderAcceptEvent(
                OrderAcceptEvent.EventData.builder()
                        .partnerId(order.getPartnerId())
                        .storeId(order.getStoreId())
                        .orderClient(order.getOrderClient())
                        .bizType(order.getBizType())
                        .orderCode(order.getOrderCode())
                        .orderState(saveData.getOrderState())
                        .timeout(cmd.getTimeout())
                        .remindTime(cmd.getRemindTime())
                        .build()
        ));
    }
}

--- OrderStateEventPublisherService ---

    @TransactionalEventListener
    public void handleAcceptEvent(OrderAcceptEvent event) {
        OrderAcceptEvent.EventData eventData = event.getEventData();

        // 缓存处理
        orderCacheManager.clear(eventData.getOrderCode(),
                OrderCacheField.ORDER_DTO,
                OrderCacheField.ORDER_OPT_HISTORY_LIST
        );

        // 餐饮订单
        if (Objects.equals(OrderBizType.ORDER.getCode(), eventData.getBizType()) || Objects.equals(OrderBizType.ADVANCE_ORDER.getCode(), eventData.getBizType())) {
            // 订单变化
            orderChangedCacheManager.add(eventData.getPartnerId(), eventData.getStoreId(), eventData.getOrderClient(), eventData.getOrderCode());

        }


        // 发送总线消息
        orderMsgService.sendOrderMessage(eventData.getPartnerId(), eventData.getOrderCode(), eventData.getOrderState(),
                MsgEvent.ACCEPT, eventData.getTimeout(), eventData.getRemindTime());
    }

Eden-Flow 组件

版本更新历史

版本号

更新时间

更新人

更新内容

1.0.02022-01-10查志伟flow融入eden
1.0.5-SNAPSHOT2023-04-10周晓航flow的task新增可拓展方法执行功能

FLow的功能

  • 通过接口泛型约束,强制代码规范化
  • 业务流程结构化,链路清晰,数据传输更简单,易维护
  • 支持拦截器,每个flow实现可自由指定一个或多个拦截器实现
  • 支持同步、异步的方式来进行流程编排
  • 可灵活指定任意流程的异常处理
  • 可控制任意流程的超时时间

Flow使用手册

定义与前端交互的报文结构(request、response)

request需要继承FlowRequest public class FlowTestRequest extends FlowRequest {...}

定义flow中的dto

dto需要继承AppDTO, AppDTO的俩个泛型类型为上面定义的 request和response
public class FlowTestDto extends FlowDto<FlowTestRequest, String> {...}

定义flow接口

flow接口需要继承FlowInf,泛型类型为上面定义的 dto、request、response, 接口需要使用@EdenFlow注解标识 关于EdenFlow的属性配置,可查看源码api

@EdenFlow(async = true, interceptor = MyFlowInterceptor.class)
public interface TestInf extends FlowInf<FlowTestDto, FlowTestRequest, String> {

    @FlowMethod(order = 1)
    boolean step1(FlowTestDto dto);

    @FlowMethod(order = 2)
    boolean step2(FlowTestDto dto);

    @FlowMethod(order = 2)
    boolean step3(FlowTestDto dto);

    @FlowMethod(order = 2, isMust = false, timeout = 100)
    boolean step4(FlowTestDto dto);

    @FlowMethod(order = 3)
    boolean step5(FlowTestDto dto);

    @FlowExceptionHandle(bindMethod = "step5", handleException = NullPointerException.class)
    boolean ex1(FlowTestDto dto);

    @FlowExceptionHandle(handleException = Exception.class, completelyMatch = false)
    boolean ex2(FlowTestDto dto);
}

定义业务流程

    /**
     * 方法入参必须是父类的第一个泛型类型(即定义的dto)
     * 方法返回值必须是Boolean值, 表示是否继续执行下一个flowMethod
     * 方法必须加上@FlowMethod注解,否则不执行,order可指定方法执行优先级,越小优先级越高
     * order最小值为1
     */
    @FlowMethod(order = 1)
    Boolean queryStoreMenuInfo(QueryStoreMenuDto dto) ;

定义异常处理流程(可选)

    /**
     * 方法入参必须是父类的第一个泛型类型(即定义的dto)
     * 方法返回值必须是Boolean值, 表示是否继续执行下一个flowMethod
     * 方法必须加上@FlowExceptionHandle,否则不生效
     * FlowExceptionHandle的属性,可查询源码api
     */
    @FlowExceptionHandle(bindMethod = "step5", handleException = NullPointerException.class)
    boolean ex1(FlowTestDto dto);

    @FlowExceptionHandle(handleException = Exception.class, completelyMatch = false)
    boolean ex2(FlowTestDto dto);

业务流程实现

/**
 * 注入到spring容器中
 * 必须继承FlowImpl,实现上一步定义的interface
 * 在该类中聚合处理业务逻辑,后续开发遵从规范编写即可
 */
@Service
public class TestImpl extends FlowImpl<FlowTestDto, FlowTestRequest, String> implements TestInf {
}

使用Flow

@RestController
@RequiredArgsConstructor
public class FlowController {

    private final TestInf testInf;

    @PostMapping("/flowTest")
    public Result<String> flowTest(@RequestBody FlowTestRequest request) {
        return testInf.start(request);
    }
}

Flow拦截器

实现接口FlowInterceptor并注入到spring容器中,然后再EdenFlow属性中配置即可

@Configuration
public class MyFlowInterceptor implements FlowInterceptor {

    /**
     * flow 执行任务前,会统一被拦截器进行拦截
     *
     * @param dto flow中的dto对象
     * @return boolean 是否继续执行
     */
    @Override
    public boolean handle(FlowDto dto) {
        MyFlowDto myDto = (MyFlowDto) dto;
        System.out.println("请求报文:" + JSON.toJSONString(myDto.getRequestVO()));
        myDto.setSessionId("666666666");
        return true;
    }

    /**
     * 返回拦截器的优先级, 越小优先级越高
     *
     * @return 优先级
     */
    @Override
    public int order() {
        return 1;
    }
}

flow核心api

/**
 * Flow的接口注解,定义flow的一些配置
 * @author Clover.z
 * @since 1.0.0
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EdenFlow {

    /**
     * 是否异步执行
     */
    boolean async() default false;

    /**
     * flow 配置的拦截器, 可配置多个
     * 拦截器需实现FlowInterceptor接口
     * @see com.freemud.eden.flow.intercept.FlowInterceptor
     */
    Class<?>[] interceptor() default {};
}

/**
 * 被注解的方法会被flow执行
 *
 * @author Clover.z
 * @since 1.0.0
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FlowMethod {

    /**
     * 执行顺序 必须>0
     * 相同的order方法标识为一组任务
     * flow为同步执行时, 这组任务会无序执行
     * flow为异步执行时, 这组任务会并发执行
     */
    int order();

    /**
     * 超时时间 正整数, 0表示永不超时
     * 单位 毫秒
     * 异步执行时生效
     */
    long timeout() default 0;

    /**
     * 是否必须执行完成
     *  配置为false则表示, 不管任务执行结果如何(或者异步中超时),都不会中断流程
     */
    boolean isMust() default true;
}

/**
 * 标志方法为异常处理方法
 * 
 * @author Clover.z
 * @since 1.0.0
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FlowExceptionHandle {

    /**
     * 异常处理顺序索引, 越小优先级越高
     */
    int index() default 0;

    /**
     * 指定哪些flowMethod中出现异常才会被处理
     * 不配置 默认所有method中的异常,都会被处理
     */
    String[] bindMethod() default {};

    /**
     * 指定flowMethod中出现指定的异常才会被处理
     * 不配置,默认所有类型异常都会被处理
     */
    Class[] handleException() default { };

    /**
     * 匹配异常时  是否使用完全匹配
     * true: 异常类型必须完全相等(配置Exception就只能捕捉Exception,其他类型的不进行捕捉)
     * false: 是指定异常的子类 也可以捕捉(配置Throwable就等于匹配全部异常)
     */
    boolean completelyMatch() default true;

}
/**
 * flow 拦截器
 * @author Clover.z
 * @since 1.0.0
 */
public interface FlowInterceptor {

    /**
     * flow 执行任务前,会统一被拦截器进行拦截
     * @param dto flow中的dto对象
     * @return boolean 是否继续执行
     */
    boolean handle(FlowDto dto);

    /**
     * 返回拦截器的优先级, 越小优先级越高
     * @return 优先级
     */
    int order();

}

 

一、初始化信息

FlowInitListener implements ApplicationListener<ContextRefreshedEvent>

重写onApplicationEvent方法 实现了自定义监听

ContextRefreshedEvent 在spring容器重载后监听执行 启动时会进行重载,从而实现了自动加载

处理步骤

1.通过容器获得所有FlowInf的实现类

2.寻找@EdenFlow 用于获取基础配置信息(是否异步、interceptor)

遍历其方法,并寻找注解,具体如下

@FlowMethod 根据isMust 与 超时时间细分 preGroup(前置Task信息) 与 taskGroup(主流程Task信息)

注意:这里将构建好的信息使用TreeMap(红黑树结构)存储

根据数据结构特性自动实行了排序,保障了后续操作的数据都是根据order从小到大排列

Key:order

Value:List<FlowTask.Task>

@FlowFinally 最终方法

@FlowExceptionHandle 异常处理器,可以自定义捕获异常与处理方法

3.构建FlowContext

3.1 flowBeanMap:

Map结构

Key:类的reference

(例如:com.freemud.sandload.alapos.business.flow.coupon.impl.QueryCouponProductFlowImpl)

Value:根据上述注解的信息构建FlowBean实体

@FlowMethod -> taskList

@FlowFinally -> finallyMethod

@FlowExceptionHandle -> handleList

@EdenFlow -> config

3.2 flowInterceptorMap

Map结构

Key:类的reference

(例如:com.freemud.sandload.alapos.business.flow.coupon.impl.QueryCouponProductFlowImpl)

Value:config所配置的interceptor

二、执行过程

程序入口: FlowInf的start方法

实现类FlowImpl

处理步骤:

1.获取FlowContext的interceptor,并执行其handle方法

2.根据FlowBean的config生成runner的实现类(AsyncRunner/SyncRunner)

3.FlowRunner开启执行runner方法

run(String flowName, FlowDto<?, ?> dto, ThreadPoolExecutor threadPoolExecutor)

flowName: 类的referenct

Dto: runner执行链路中的共享参数类

threadPoolExecutor: 线程池

4.根据flowName从FlowContext获取初始化存储的FlowBean实体

5.遍历FlowBean的taskList执行,根据初始化时分类好的preGroup与taskGroup,执行invoke方法(异步形式需要额外处理)

6.异常处理 使用flowBean的handleList遍历处理异常

7.执行flowBean的finalMethod方法

三、补充说明多线程执行方式

使用AsyncRunner时

1.线程池使用

FlowAutoConfig中@ConditionalOnMissingBean接收 无注入类的情况(当前线程池接口无实现类)
Executors.newCachedThreadPool() 默认配置

2.执行顺序

根据taskGroup和preGroup构建TaskWrapper

CompletableFuture.runAsync 开启多线程

返回futures和wrappers

根据futures的执行时间判断是否超时

根据wrappers的状态判断是否执行成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值