一起探讨下web请求流程的代码结构设计(简单以交易为栗子)

发表于:2018-6-25 11:52

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:明人不说暗话___我喜&    来源:51testing软件测试网采编

#
流程
分享:
  今天这篇文章所涉及的相关观点做法,按照我们公司某位架构师的说法就是,架构这东西没有对错之分,集思广益,不一定我的做法就是对的,不一定我的做法就是最优的,小编和大家一起探讨探讨。
  一、场景
  首先我们模拟一个SDK交易链路的场景,对交易不是很懂的同学可以先移步至微信支付了解下大概业务,其实小编也不是特别精通交易,刚实习,但这不影响本文哈!一笔交易的生命周期大致可以分为这么几个阶段,即预下单---支付---交易通知,什么意思呢?拿微信扫码点餐付款来说,首先微信客户端扫码之后会先生成一笔订单,当预下单完成之后,客户端会触发SDK发起支付请求,然后用户输入密码完成支付,支付之后会发送订单状态通知用户,我们最后才能知道我到底付款成功没有。
  下面是微信扫码支付业务时序图:
  既然业务步骤是明确的,我们首先考虑代码方面应该怎么去架设会好一点,首先猜想能否定义三个生命周期方法,如下:
  // 前置业务---预下单
  beforeService()
  // 核心业务处理----支付
  doService()
  // 后置业务处理----通知
  afterService()
  但是,实际业务每个步骤往往非常复杂,也就说比如我们有可能在预下单的时候会加入优惠信息,判断当前用户这笔订单是否符合优惠,如果优惠后的金额为0,还有可能不需要任何的支付处理,直接就发送通知给到用户。还有一点我们业务通常还要区别是刷卡预下单还是扫码预下单,扫码预下单还要区分是主扫还是被扫,因为这几种区分是实际和线下场景相匹配的,为了降低耦合,这时我们会分别提供一个接口来处理各种请求。
  既然是每种场景对应一个接口,我们又可以想到,通常接口都是需要进行安全性校验的,如MD5、RSA及SHA1各种加密,保证交易的安全性。为了和核心业务区分开来,我们通常会在前置业务中甚至在filter端就把这些校验就完成掉,避免如果校验出错进到业务层去。既然每个接口都有诸如前置校验----核心处理----后置处理的逻辑,那我们可以把上面的设想再细分一下,即:
  // 前置业务---参数校验,加密等操作
  beforeService()
  // 核心业务处理----预下单、支付等核心业务
  doService()
  // 后置业务处理----保存订单、保存流水、发送通知等后置业务
  afterService()
  那么代码层应该如何去设计呢?可否这样,像下面的UML图:
  下面对各个组件进行讲解:
  SDKService
  最顶层的抽象,一般业务类不需要实现它,需要需要扩展该接口,直接继承即可。
  SDKNoCardService
  无卡交易相关能力,扩展SDKService
  SDKRomoteService
  调用远程方法交易相关能力,扩展SDKService,如远程调用微信下单接口
  AbstractSDKService
  抽象类,实现SDKService,同时定义上面的三个声明周期方法,相当于一个模板,具体实现由子类去重写即可
  SDKNoCardServceImpl
  无卡交易业务实现类,继承抽象类,因为需要获取卡号,所以实现SDKNoCardService接口
  SDKCardTradeServiceImpl
  刷卡交易业务实现类,继承抽象类,因为需要调用远程服务,所以实现SDKRomoteService接口
  这样做的好处在于,如果我需要引入一个新的需求,比如说小额免签免密,那么我只需要创建一个新类继承抽象类AbstractSDKService即可,如果小额免签免密需要一个新的能力,那么只需要新增一个接口扩展SDKService即可。下面给出相应的测试代码:
  SDKService.java
  package com.example.interfaces;
  import java.util.Map;
  import javax.servlet.http.HttpServletRequest;
  public interface SDKService<T,R> {
      /**
      * 业务处理
      * @param request
      * 请求
      * 
      * @return
      */
      public R service(HttpServletRequest request);
      
      /**
      * 业务处理
      * @param body
      * 客户端请求消息体对象
      * 
      * @param request
      * 请求
      * 
      * @return
      */
      public R service(T body, HttpServletRequest request);
      
      /**
      * 业务处理
      * @param body
      * 客户端请求消息体对象
      * 
      * @param headers
      * 客户端请求头信息对象
      * 
      * @return
      */
      public R service(T body, Map<String, String> headers);
  }
  SDKRemoteService.java
  package com.example.interfaces;
  public interface SDKRemoteService<T, R> extends SDKService<T, R>{
      
      /**
       * 调用远程交易,比如说调用微信支付
       * @return
       */
      public R call();
  }
  SDKNoCardService.java
  package com.example.interfaces;
  public interface SDKNoCardService<T, R> extends SDKService<T, R> {
      /**
       * 获取卡号
       * @return
       */
      public R getCardNo();
  }
  AbstractSDKService.java
  package com.example.interfaces;
  import java.util.HashMap;
  import java.util.Map;
  import javax.servlet.http.HttpServletRequest;
  /**
   * 传入参数T,返回结果R
   * 
   * @author huangjiawei
   *
   * @param <T>
   * @param <R>
   */
  public abstract class AbstractSDKService<T, R> implements SDKService<T, R> {
      
      // 前置处理,比如可以对参数进行校验,由子类选择性重写
      protected void beforeService() {
      };
      
      /**
       * 核心业务方法,由不同业务子类选择性去重写
       * 
       * @return
       */
      protected abstract R doService();
      
      // 后置处理,比如记录业务结果,由子类选择性重写
      protected void afterService() {
      };
      
      /**
       * 接受请求
       */
      @Override
      public R service(HttpServletRequest request) {
      // 模拟请求体
      System.err.println("service with only request!");
      return this.service(null, request);
      }
      
      /**
       * 处理请求体
       */
      @Override
      public R service(T body, HttpServletRequest request) {
      // 模拟请求体
      Map<String, String> headers = new HashMap<>();
      System.err.println("service with body and request!");
      return this.service(body, headers);
      }
      
      /**
       * 处理请求头和实际业务逻辑
       */
      @Override
      public R service(T body, Map<String, String> headers) {
      System.err.println("service with bofy and headers!");
      R response = null;
      
      // 记录日志
      
      //......
      
      // 前置处理
      beforeService();
      
      // 核心处理
      doService();
      
      // 后置处理
      afterService();
      
      // ......
      
      return response;
      }
  }
  SDKCardTradeServiceImpl.java
  package com.example.interfaces;
  public class SDKCardTradeServiceImpl<T,R> extends AbstractSDKService<T,R> implements SDKRemoteService<T,R> {
      
      // 在这里可以选择性重写父类的before()方法
      @Override
      protected void beforeService() {
      super.beforeService();
      System.err.println("SDKCardTradeServiceImpl before!");
      }
      
      @Override
      protected R doService() {
      // 处理刷卡特定业务逻辑
      System.err.println("SDKCardTradeServiceImpl doService!");
      return null;
      }
      
      /**
       * 我同时拥有调用远程方法的能力,所以我实现了SDKRemoteService接口
       */
      @Override
      public R call() {
      return null;
      }
      
      // 在这里可以选择性重写父类的after()方法
      @Override
      protected void afterService() {
      super.afterService();
      System.err.println("SDKCardTradeServiceImpl afterService!");
      }
  }
  SDKNoCardServceImpl.java
  package com.example.interfaces;
  /**
   * 无卡交易核心业务处理
   * @author huangjiawei
   *
   */
  public class SDKNoCardServceImpl<T,R> extends AbstractSDKService<T,R> implements SDKNoCardService<T,R> {
      
      // 在这里可以选择性重写父类的before()方法
      
      @Override
      protected R doService() {
      return null;
      }
      
      /**
       * 我同时拥有获取卡号的能力,所以我实现了SDKNoCardService接口
       */
      @Override
      public R getCardNo() {
      return null;
      }
      
      // 在这里可以选择性重写父类的after()方法
      @Override
      protected void afterService() {
      super.afterService();
      System.err.println("SDKNoCardServceImpl 后置处理");
      }
  }
  Main.java
  package com.example.interfaces;
  public class Mian {
  public static void main(String[] args) {
      SDKCardTradeServiceImpl<String,String> o = new SDKCardTradeServiceImpl<>();
      o.service(null);
      System.err.println("======================================");
      
      SDKNoCardServceImpl<String, String> p = new SDKNoCardServceImpl<>();
      p.service(null);
      }
  }
  程序输出:
  service with only request!
  service with body and request!
  service with bofy and headers!
  SDKCardTradeServiceImpl before!
  SDKCardTradeServiceImpl doService!
  SDKCardTradeServiceImpl afterService!
  ======================================
  service with only request!
  service with body and request!
  service with bofy and headers!
  SDKNoCardServceImpl 后置处理
  注意我们实际调用的时候只需要调用顶层的service()即可,面向接口编程。
  二、总结
  认真看,其实这个已经定义好了一个大模板了,会非常实用的。小编认为,一般的设计思路是先采用一个顶层接口进行高层抽象,然后创建其他接口扩展顶层接口,在顶层接口下建立一层抽象层,提供默认的实现,定义模板方法给不同的业务类重写。当然,处理本文的做法,一般处理web请求我们也可以通过AOP切面的方式对前置处理和后置处理进行拦截,即利用环绕通知即可,但是,采用aop的缺点可能会获取不到request请求的参数(没测过,大佬们说会,我也不清楚为什么,等你来告诉小编!),本文的方法利用了java面向对象的特性,扩展性会更好点,不过略抽象!
  本文讲解的比较抽象,很多地方可能没有讲的特别清楚,同学们可以评论,我们一起探讨探讨。


上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号