前言
由于业务需要,近期看了一下平台应用如何调用「招商银行(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(); } } }
上述代码,快速的实现了业务功能。很直观的把业务需求,转换成最直观的代码。
优点:
- 快速实现功能
- 代码易读,直观
缺点:
- 无法统一请求银行的共有方法
- 业务层调用时,都要写条件判断且参数需转换成特定银行的参数字段,导致代码冗余
- 新增一个其他银行通道时,需在每一个业务层增加判断,不易维护
接口
所谓的「接口」就是把公共的请求,抽象成一个接口。统一管理,不同的银行通道实现接口。再通过设计模式(策略模式、工厂模式)获取具体的实现。业务层调用工厂类获取具体接口实现即可。
-
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(); } }
上述代码,统一了接口,方便了扩展和后期维护;
优点:
- 统一了请求银行的方法
- 业务层调用时,使用
BankTemplateFactory
工厂类,获取具体实现 - 新增一个其他银行通道时,需要实现
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); } }
总结
这段时间,每晚入睡了,大脑中总在思考当前的业务应该如何抽象,如何设计。自己已经魔怔了!
其实我们写代码,本质是让现实世界的需求,在计算机世界中被理解和执行。如果只是这样,我可能不会要求自己的代码写的像 诗 一样!
编码的最终形态,表达了对业务理解的抽象能力,也表达了对使用语音特性的理解(比如面向对象、面向接口、面向抽象)。有时候固执的要求自己写出所谓的 完美 代码,那只会让自己陷入痛苦之中。
在时间、成本、维护性等方面寻找平衡,放弃过渡思考、过渡设计。