前言
随着企业发展,内部系统需要从不同的第三方服务提供商(如天气数据、金融数据、身份数据等)获取数据。每个服务的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作为调用方法。同时也是内部系统调用的入口。
@Service
class 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)
}
}
策略实现
static 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
}
}
// 普通调用
static 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
}
}
// 条件选择策略
static 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() {
...
}
}
总结
以上示例展示了如何设计和实现一个多数据源策略组合模块,包括 串行调用、并行调用等。也可以根据需要实现更多的策略组合模式,为复杂的业务场景提供支持。这是一个抛砖引玉的案例,实际上数据源平台还需要其他功能的功能的支撑。如果你有好的思路和想法,可以评论区留言,我们下期见。
关于领创集团