技术创想110| 数据源系统策略组合模块的设计与实现

文摘   科技   2024-10-31 17:04   北京  

前言

随着企业发展,内部系统需要从不同的第三方服务提供商(如天气数据、金融数据、身份数据等)获取数据。每个服务的API接口各不相同,直接对接会导致开发、维护成本不断上升。一个合格的数据源系统会涉及数据标准化模块、API管理模块、组合策略模块、认证与安全模块、监控与告警模块等等。今天我们从组合策略模块出发,带大家一步一步来进行相关的设计和代码实现,希望对大家有所帮助。

组合策略模块的作用

随着业务需求的多样化,单一数据源接口往往难以全面满足各类场景需求。根据不同业务场景提供灵活的调用策略,API平台的灵活性和功能扩展性得以提升,从而更有效地保障业务需求的满足。以下是比较常见的组合策略方式。

串行调用:按照顺序依次调用多个数据源API,适合数据依赖关系明确的情况。

并行调用:同时调用多个API,汇聚数据后返回结果,适合数据独立的场景。

缓存策略:引入缓存层,避免频繁调用外部数据源,降低延迟。

选择策略:根据响应时间或数据质量评分,选择最佳数据源。

组合策略方法的设计

CallStrategy 为父类,负责制定每种策略需要实现的能力,包括策略判断、实际调用和结果解析等,见以下类图。

组合策略方法调用的结构设计

内部系统调用组合策略方法时需要指定调用规则,根据复杂程度可以拆分成几种,因为json结构易于管理,所以下面例子都是json格式。

  • 普通调用

最简单的调用方式

{  "type": "GENERAL", //指定单一调用的类型  "adapter": {    "id": "adapterName", //调用第三方源的内部名字    ...  }}


  • 串行调用

调用一个节点后,再调用第二个节点。在此基础上也可以扩展成多个。同时支持扩展成是否根据前一个结果作为后一次调用的入参等等,下面是最简洁的串行调用结构。

// 使用组合策略的类型,可以是GENERAL、PARALLEL、SWITCH等{  "type": "GENERAL",  "adapter": {    "id": "adapterName1" //调用第三方源的内部名字  },  "next": { // 串行节点,可多级嵌套    "type": "GENERAL",    "adapter": {      "id": "adapterName2" //调用第三方源的内部名字    },"next": { // 串行节点,可多级嵌套      ...    }  }}
  • 条件选择

根据条件选择哪个节点进行调用。也可以扩展为多个。其中{parameter}设置为入参的名字,{condition1}和{condition2}为判断条件。

{  "type": "SWITCH", //调用的类型  "children": [{    "type": "GENERAL",    "adapter": {      "id": "adapterName1"    }  }, {    "type": "GENERAL",    "adapter": {      "id": "adapterName2"    }  }],  "childrenMeta": [{    "percent": {      "type": "EQUAL", // 在此基础上也可以增加百分比调用类型      "data": {        "request.{parameter}": "{condition1}"      }    }  }, {    "percent": {      "type": "EQUAL",      "data": {        "request.{parameter}": "{condition2}"      }    }  }]}

其他条件组合策略方式也可以在此基础上增加,由于篇幅原因不再扩展。为了方便扩展可以将上述结构放置在数据库中,方便查询。

实现步骤

4.1 策略参数设计

根据上方的数据结构设计,我们可以把此信息分解为以下几部分。

  • 策略枚举,我们要支持哪些策略。例如:普通调用、串行调用、并行调用、选择策略等等。

enum ChainNodeTypeEnum {    GENERAL,PARALLEL_ALL,SWITCH, ……}
  • 系统信息

对应数据结构中adapter部分

/**  "adapter": {    "id": "adapterName", //调用第三方源的内部名字    ...  } */class AdapterDTO {
String id ...}
  • 是否继续调用的条件枚举

依次为失败时调用、成功时调用、始终调用、不调用

enum CallNextStrategyEnum {    FAIL, SUCCESS, ALWAYS, NEVER}

将上方三个整理后,最终的ChainNodeDTO结构如下:

class ChainNodeDTO {    //策略枚举    ChainNodeTypeEnum type    AdapterDTO adapter    // 下一个调用节点    ChainNodeDTO next    // 子节点列表    List<ChainNodeDTO> children    // 子节点列表描述信息    List<Map> childrenMeta    // 调用下一个节点的条件    CallNextStrategyEnum callNextStrategy    ...}

4.2 策略实现类设计

因为涉及到链式调用,所以采用了递归的方式,先准备一个基本的调用方法,策略实现时都以startCall作为调用方法。同时也是内部系统调用的入口。

@Serviceclass AdapterDataService {        @Autowired    List<CallStrategy> strategyList
ChainNodeDTO getAdapterChain(RequestDTO params) { // 从数据库中读取ChainNodeDTO结构 ... }
/** * 根据调用链调用adapter服务,并获取各个adapter的结果 */ Object startCall(RequestDTO params, ChainNodeDTO chainNode, Object lastResult) { // 调用服务 CallStrategy targetStrategy = strategyList.find { it.canCall(chainNode) } return targetStrategy.call(this, params, chainNode, lastResult) }}

策略实现

@CompileStaticstatic abstract class CallStrategy {
// 调用策略 abstract Object call(AdapterDataService parent, RequestDTO params, ChainNodeDTO chainNodeDTO)
// 是否能够调用当前节点 abstract boolean canCall(ChainNodeDTO chainNode)
// 是否能够调用下一个节点 boolean canCallNext(ChainNodeDTO chainNode, boolean successFlag) { CallNextStrategyEnum strategy = chainNode.callNextStrategy ?: CallNextStrategyEnum.FAIL if (strategy == CallNextStrategyEnum.FAIL) { return !successFlag } else if (strategy == CallNextStrategyEnum.SUCCESS) { return successFlag } else if (strategy == CallNextStrategyEnum.ALWAYS) { return true } else if (strategy == CallNextStrategyEnum.NEVER) { return false } return true }}
// 普通调用@Componentstatic class GeneralStrategy extends CallStrategy {
boolean canCall(ChainNodeDTO chainNode) { return chainNode.type == ChainNodeTypeEnum.GENERAL }
Object call(AdapterDataService parent, RequestDTO params, ChainNodeDTO chainNodeDTO, Pair<ICode, Object> lastResult) { AdapterEntity adapterEntity = adapterMapper.selectByMap(['adapter_id': targetAdapter.id] as Map)?.find { true } ... //判定是否需要调用下一级 boolean canCallNext = canCallNext(chainNodeDTO, isSuccess) if (canCallNext && chainNodeDTO.next) {// 调用下一级(如果有的话) log.info("${LOGGER_PREFIX} targetAdapter:${targetAdapter.id}, call next") return parent.startCall(params, chainNodeDTO.next, resultPair) } return resultPair }}
// 条件选择策略@Componentstatic class SwitchStrategy extends CallStrategy {
boolean canCall(ChainNodeDTO chainNode) { return chainNode.type == ChainNodeTypeEnum.SWITCH }
Object call(AdapterDataService parent, RequestDTO params, ChainNodeDTO chainNodeDTO, Object lastResult) { // 根据流量比计算该使用哪个 List<Map> switchStrategy = chainNodeDTO.childrenMeta ?: [] // 根据策略调用不同类型 ChainNodeDTO targetNode = null if (StringUtils.equalsIgnoreCase(type, 'PERCENT')) { targetNode = byPercent(params, chainNodeDTO, switchStrategy) } else if (StringUtils.equalsIgnoreCase(type, 'EQUAL')) { targetNode = byEqual(params, chainNodeDTO, switchStrategy, lastResult) } if (!targetNode) { targetNode = byDefault(params, chainNodeDTO, switchStrategy) }
return parent.startCall(params, targetNode, resultPair) }
ChainNodeDTO byEqual() { ... }
ChainNodeDTO byPercent() { ... }
ChainNodeDTO byDefault() { ... }
}

总结

以上示例展示了如何设计和实现一个多数据源策略组合模块,包括 串行调用、并行调用等。也可以根据需要实现更多的策略组合模式,为复杂的业务场景提供支持。这是一个抛砖引玉的案例,实际上数据源平台还需要其他功能的功能的支撑。如果你有好的思路和想法,可以评论区留言,我们下期见。



关于领创集团

(Advance Intelligence Group)
领创集团成立于 2016年,致力于通过科技创新的本地化应用,改造和重塑金融和零售行业,以多元化的业务布局打造一个服务于消费者、企业和商户的生态圈。集团旗下包含企业业务和消费者业务两大板块,企业业务包含 ADVANCE.AI 和 Ginee,分别为银行、金融、金融科技、零售和电商行业客户提供基于 AI 技术的数字身份验证、风险管理产品和全渠道电商服务解决方案;消费者业务 Atome Financial 包括亚洲领先的数字金融服务平台 Atome 等。2021年 9月,领创集团宣布完成超4亿美元 D 轮融资,融资完成后领创集团估值已超 20亿美元,成为新加坡最大的独立科技创业公司之一。




领创集团Advance Group
领创集团是亚太地区AI技术驱动的科技集团。
 最新文章