从对接银行出发,如何看待实现方式?

作者: gentlelucky | 2503 字, 5 分钟 | 评论 | 2024-11-29 | 分类: Develop

bank, code, design-pattern, thinking

前言

由于业务需要,近期看了一下平台应用如何调用「招商银行(CMB)」和「平安银行(PAB)」接口进行业务交互。于是在思考,针对于这样的业务场景,是否需要考虑 设计模式、是否需要考虑 后期维护 、是否考虑 易扩展性 等。

接下来,我将从对接银行接口出发,列举出三套实现方案。从 基础实现 -> 接口 -> 高级抽象 维度来实现。

基础实现

所谓的「基础实现」就是过程式编码,满足业务功能,不考虑任何扩展、设计模式等因素;主打一个能用就行。

  • CmbService:招商银行的接口逻辑

    // 招商银行接口
    public class CmbService {
      // 获取子账号余额
      public CmbSubAccountBalance getSubAccountBalance(CmbSubAccountBalanceParam param) {
        // 请求招商银行
        String s = this.request(param);
        // 响应消息序列化成对象
        return JSONUtil.toBean(s, CmbSubAccountBalance.class);
      }
    
      public String request(Object param) {
        //...请求银行,涉及到的操作
        // 1. 加签
        // 2. 加密
        // 3. 解密
      }
    
    }
    
  • PabService:平安银行的接口逻辑

    // 平安银行接口
    public class PabService {
      // 获取子账号余额
      public PabSubAccountBalance getSubAccountBalance(PabSubAccountBalanceParam param) {
        // 请求平安银行
        String s = this.request(param);
        // 响应消息序列化成对象
        return JSONUtil.toBean(s, PabSubAccountBalance.class);
      }
    
      public String request(Object param) {
        //...请求银行,涉及到的操作
        // 1. 加签
        // 2. 加密
        // 3. 解密
      }
    
    }
    
  • BizService:业务层调用

    public class BizService {
      // 获取子账号余额
      public String getSubAccountBalance(AccountParam param) {
        // 判断银行类型
        if("CMB".equals(param.getBankType())) {
          // 请求参数转换成招行接口的入参
          CmbSubAccountBalanceParam cmbParam = new CmbSubAccountBalanceParam(param);
          // 请求招商银行获取子账户余额
          CmbSubAccountBalance accountBalance = cmbSubAccountBalance.getSubAccountBalance(cmbParam);
          return accountBalance.getBalance();
        }
        if("PAB".equals(param.getBankType())) {
          // 请求参数转换成平安接口的入参
          PabSubAccountBalanceParam pabParam = new PabSubAccountBalanceParam(param);
          // 请求平安银行获取子账户余额
          PabSubAccountBalance accountBalance = pabSubAccountBalance.getSubAccountBalance(pabParam);
          return accountBalance.getBalance();
        }    
      }
    }
    

上述代码,快速的实现了业务功能。很直观的把业务需求,转换成最直观的代码。

优点:

  1. 快速实现功能
  2. 代码易读,直观

缺点:

  1. 无法统一请求银行的共有方法
  2. 业务层调用时,都要写条件判断且参数需转换成特定银行的参数字段,导致代码冗余
  3. 新增一个其他银行通道时,需在每一个业务层增加判断,不易维护

接口

所谓的「接口」就是把公共的请求,抽象成一个接口。统一管理,不同的银行通道实现接口。再通过设计模式(策略模式、工厂模式)获取具体的实现。业务层调用工厂类获取具体接口实现即可。

  • BankTemplate:银行统一接口

    // 银行统一接口
    public interface BankTemplate {
      // 获取子账号余额
      SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param);
    }
    
  • CmbBankTemplate:招商银行接口实现

    // 招商银行接口实现
    public class CmbBankTemplate implements BankTemplate {
      // 获取子账号余额
      public SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param) {
        // 入参转换成招行请求参数字段
        Map<String, String> paramMap = this.buildMap(param);
        // 请求银行
        String response = this.request(paramMap);
        // 把 response 响应转换成统一的 SubAccountBalance 对象
        return this.buildSubAccountBalance(response);
      }
    
    
      public String request(Object param) {
        //...请求银行,涉及到的操作
        // 1. 加签
        // 2. 加密
        // 3. 解密
      }
    }
    
  • PabBankTemplate:平安银行接口实现

    // 平安银行接口实现
    public class PabBankTemplate implements BankTemplate {
      // 获取子账号余额
      public SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param) {
        // 入参转换成平安请求参数字段
        Map<String, String> paramMap = this.buildMap(param);
        // 请求银行
        String response = this.request(paramMap);
        // 把 response 响应转换成统一的 SubAccountBalance 对象
        return this.buildSubAccountBalance(response);
      }
    
    
      public String request(Object param) {
        //...请求银行,涉及到的操作
        // 1. 加签
        // 2. 加密
        // 3. 解密
      }
    }
    
  • BankTemplateFactory:BankTemplate工厂类

    public class BankTemplateFactory {
      public static BankTemplate getTemplate(String type) {
        // 招商银行
        if ("CMB".equals(type)) {
          return new CmbBankTemplate();
        }
        // 平安银行
        if ("PAB".equals(type)) {
          return new PabBankTemplate();
        }
      }
    }
    
  • BizService:业务层调用

    public class BizService {
      // 获取子账号余额
      public String getSubAccountBalance(SubAccountBalanceParam param) {
        // 根据银行类型获取具体BankTemplate实现
        BankTemplate bankTemplate = BankTemplateFactory.getTemplate(param.getBankType());
        // 请求银行获取子账户余额
        SubAccountBalance subAccountBalance = bankTemplate.getSubAccountBalance(param);
        return subAccountBalance.getBalance();
      }
    }
    

上述代码,统一了接口,方便了扩展和后期维护;

优点:

  1. 统一了请求银行的方法
  2. 业务层调用时,使用 BankTemplateFactory 工厂类,获取具体实现
  3. 新增一个其他银行通道时,需要实现 BankTemplate 接口,以及在 BankTemplateFactory 工厂类中加判断(可以优化成不需要手动加判断,不在此次谈论范围内)

第二套「接口」方案,已经解决了第一套「基础实现」方案中的不足的方面,满足了业务系统后续扩展和后续维护等实际需求。

高级抽象

所谓的「高级抽象」就是分析业务后,把业务模型抽象化。那么对接银行的业务如何抽象化呢?

通过分析发现,业务需求是请求银行接口获取相应的数据,那么抽象出来就是需要一个请求银行的接口( BankInterface )。

不同银行请求入参不同、加解密方式不同,所以需要有一个适配器去转换( BankInterfaceAdapter )。

两个银行都提供了「查询子账号余额」的接口,但请求的 URL 是不同的,为了简化URL设置,可以自定义注解( @BankApi(path = “/V1.0/bedl/SubAccountBalanceQuery”, method = HttpMethod.POST) )。

大致代码逻辑如下:

  • BankInterface:银行接口

    /**
     * 银行接口
     */
    public interface BankInterface {
    
        /**
         * 银行接口适配器
         * @return
         */
        BankInterfaceAdapter adapter();
    
        /**
         * 子账号余额查询
         * @param param
         * @return
         */
        JSON subAccountBalanceQuery(BankApiParam param);
    
    }
    
  • BankInterfacePab:平安银行接口

    /**
     * 平安银行接口
     */
    @Type(value = BankType.PAB)
    public interface BankInterfacePab extends BankInterface {
    
        @BankApi(path = "/V1.0/bedl/SubAccountBalanceQuery", method = HttpMethod.POST)
        @Override
        JSON subAccountBalanceQuery(BankApiParam param);
    }
    
  • BankInterfaceAdapter:银行适配器

    /**
     * 银行适配器
     */
    public interface BankInterfaceAdapter {
    
        /**
         * 构建请求参数
         * @param obj
         * @return
         */
        BankApiParam buildParam(Object obj);
    
        /**
         * 前置处理
         * @param param
         */
        void preProcessor(BankApiParam param);
    
        /**
         * 后置处理
         * @return
         */
        String postProcessor(String msg);
    
        /**
         * 发送请求
         * @param param
         * @return
         */
        String process(BankApiParam param);
    
        /**
         * 子账号余额适配
         * @param json
         * @return
         */
        SubAccountBalance subAccountBalanceQuery(JSON json);
    
    }
    
  • BankInterfaceAdapterPab:平安银行适配

    /**
     * 平安银行适配
     */
    public class BankInterfaceAdapterPab implements BankInterfaceAdapter {
    
        public final static BankInterfaceAdapterPab INSTANCE = new BankInterfaceAdapterPab();
    
        @Override
        public BankApiParam buildParam(Object obj) {
            return null;
        }
    
        @Override
        public void preProcessor(BankApiParam param) {
    
        }
    
        @Override
        public String postProcessor(String msg) {
            return null;
        }
    
        @Override
        public String process(BankApiParam param) {
            return null;
        }
    
        @Override
        public SubAccountBalance subAccountBalanceQuery(JSON json) {
            return null;
        }
    }
    
  • BankProxy:银行代理类

    /**
     * 银行代理类
     */
    public interface BankProxy {
    
        /**
         * 获取子账号流水
         * @param param
         * @return
         */
        SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param);
    }
    
  • BankProxyImpl:银行代理类实现

    /**
     * 银行代理类实现
     */
    public class BankProxyImpl implements BankProxy {
    
        private final BankInterface bankInterface;
        private final BankInterfaceAdapter bankInterfaceAdapter;
    
        public BankProxyImpl(BankInterface bankInterface) {
            this.bankInterface = bankInterface;
            this.bankInterfaceAdapter = bankInterface.adapter();
        }
    
        @Override
        public SubAccountBalance getSubAccountBalance(SubAccountBalanceParam param) {
            BankApiParam bankApiParam = bankInterfaceAdapter.buildParam(param);
            JSON json = bankInterface.subAccountBalanceQuery(bankApiParam);
            return bankInterfaceAdapter.subAccountBalanceQuery(json);
        }
    }
    
  • RpcClientProxy:请求银行RPC,反射

    /**
     * 请求银行RPC,反射
     */
    public class RpcClientProxy implements InvocationHandler  {
    
        @Getter
        private final RpcClientProxyFactoryBean factory;
    
        public RpcClientProxy(RpcClientProxyFactoryBean factoryBean) {
            this.factory = factoryBean;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
        }
    }
    
  • RpcClientProxyFactoryBean:RPC请求代理工厂

    /**
     * RPC请求代理工厂
     */
    public class RpcClientProxyFactoryBean implements FactoryBean<BankInterface> {
    
        private final Class<? extends BankInterface> serviceInterface;
    
        public RpcClientProxyFactoryBean(Class<? extends BankInterface> clz) {
            this.serviceInterface = clz;
        }
    
        @Override
        public BankInterface getObject() throws Exception {
            return (BankInterface) Proxy.newProxyInstance(this.serviceInterface.getClassLoader(),
                    new Class[]{this.serviceInterface},
                    new RpcClientProxy(this));
        }
    
        @Override
        public Class<?> getObjectType() {
            return this.serviceInterface;
        }
    
        @Override
        public boolean isSingleton() {
            return true;
        }
    }
    
  • BankInterfaceManager:统一银行接口管理

    /**
     * 统一银行接口管理
     */
    public class BankInterfaceManager {
    
        /**
         * 获取具体的银行代理类
         * @param bankType
         * @return
         */
        public BankProxy getBankProxy(BankType bankType) {
            BankInterface bankInterface = null;
            return new BankProxyImpl(bankInterface);
        }
    }
    

总结

这段时间,每晚入睡了,大脑中总在思考当前的业务应该如何抽象,如何设计。自己已经魔怔了!

其实我们写代码,本质是让现实世界的需求,在计算机世界中被理解和执行。如果只是这样,我可能不会要求自己的代码写的像 一样!

编码的最终形态,表达了对业务理解的抽象能力,也表达了对使用语音特性的理解(比如面向对象、面向接口、面向抽象)。有时候固执的要求自己写出所谓的 完美 代码,那只会让自己陷入痛苦之中。

在时间、成本、维护性等方面寻找平衡,放弃过渡思考、过渡设计。

相关文章

gentlelucky

作者

gentlelucky

后端 & Java研发工程师。 在 GitHub 关注我。在我的 Telegram 频道了解更多。