借助SPI 解决复杂业务扩展问题
首先我们了解下何为SPI,SPI全称:Service Provider Interface,由一方提供接口规范,另一方负责具体实现。其理念跟软件设计模式中的策略模式有点类似,前者是业务架构设计维度,后者是接口编程维度。
SPI优势:有效解决了代码高耦合问题,避免使用大量的if else 嵌套逻辑,大大提高了系统的可维护性和扩展性。对于复杂的业务场景,可以实现系统间的解耦,通过Restful接口完成交互,又避免了不同的商户接入带来的重复开发工作。
JDK原生用法
通过规则约定加规范的方式,按照接口名称定义配置文件,并将处理不同业务逻辑的实例类添加到配置文件中,通过类加载器完成加载。
/**
* 创建订单接口
* @author onlyone
*/
public interface OrderService {
String createOrder(AddOrderParam addOrderParam);
}
然后在 “META-INF/services”路径下创建文件 “com.boot.service.OrderService”,文件内容为:
com.boot.service.impl.TaobaoOrderService
com.boot.service.impl.TianmaoOrderService
最后借助jdk原生的ServiceLoader去META-INF/services目录下加载配置文件并将类实例化,完成调用。
代码示例:
https://github.com/aalansehaiyang/spi-example
Dubbo 框架用法
jdk原生框架采用全部加载机制,不管需不需要。导致系统初始启动时会占用大量系统资源,耗时较长。dubbo对其进行改造,重写加载机制,采用按需加载。
- 自定义注解 @SPI
- 重写加载类ExtensionLoader
- 并大量使用缓存,提升性能
- 提供getExtension方法,可以获取具体实现类
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
....各种校验
} else {
//缓存中获取扩展加载器,为空则进行新建
ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
}
return loader;
}
}
public T getExtension(String name) {
。。。参数校验
Holder<Object> holder = cachedInstances.get(name); //从缓存中获取持有者
if (holder == null) { //如果为空,创建一个持有者
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get(); //从持有者获取实现类
if (instance == null) { //如果为空
synchronized (holder) {
instance = holder.get();
//双重判空检查
if (instance == null) {
//采用懒加载模式加载实现类
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
架构衍生
随着一个企业越做越大,沉淀了很多数据,会通过API开放形式接入网关,给外部的ISV提供原始数据服务,进一步打造业务生态圈。但是这种模式有个缺点,规范和实现由内部控制,外部单向依赖。
如果某些业务场景需要双向依赖,可以考虑使用SPI模式,由平台方定义接口规范,第三方来实现内部逻辑,通过HTTP协议来调用。可以理解成SPI是传统API的反向调用。
存在这样一种业务场景,平台需要向开发者请求数据信息。比如一些大电商卖家都有自己的仓库,本地库存系统,自己的售卖网站,同时也在淘宝、京东、拼多多等三方平台售卖商品,如果想共享一套库存数据要怎么设计,可以考虑SPI。
图引自程序架道
首先平台方定义业务场景,并在场景下定义SPI接口规范,接入方按规范实现接口逻辑(比如:调用本地数据库判断是否有库存),并将API服务注册到网关。当用户访问平台系统时,会根据访问的店铺、IP地址、用户数据等信息,路由寻址到指定的商户系统,完成数据交互,并进行后续业务流程。
当然,不同公司的技术实力参差不齐,系统稳定性、接口响应速度都不一样,平台系统的容错能力、超时机制要深入建设,避免局部慢请求,引发雪崩效应。
小结:
- 提供了内部数据的开放能力,借助ISV的开发能力,形成一个大的业务生态圈
- 外部系统数据可以以插件的形式注册到平台,由平台指定统一规范和路由能力, 满足更复杂的业务诉求。
- 无人驾驶系列——深度学习笔记:Tensorflow的安装-windows系统
- 2018年12大顶级云安全威胁
- 缤果盒子为域名意识打call 六位数秒下bingobox.com
- 用Qt写软件系列二:QCookieViewer(浏览器Cookie查看器)
- 用Qt写软件系列一:QCacheViewer(浏览器缓存查看器)
- 用Qt写软件系列三:一个简单的系统工具(上)
- ChartDirector应用笔记(三)
- 汇编语言 手记9
- 程序员一年写百万行代码是什么体验?这肯定是个Bug
- ChartDirector应用笔记(二)
- ChartDirector应用笔记(一)
- DB Cache
- oracle多用户并发及事务处理
- 数据中心整合会导致更高的云价格吗?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Python xpath表达式如何实现数据处理
- Python脚本破解压缩文件口令实例教程(zipfile)
- 使用keras实现Precise, Recall, F1-socre方式
- Python Django搭建网站流程图解
- Pandas对DataFrame单列/多列进行运算(map, apply, transform, agg)
- keras自定义损失函数并且模型加载的写法介绍
- pandas DataFrame运算的实现
- Python流程控制语句的深入讲解
- 在keras里面实现计算f1-score的代码
- Keras官方中文文档:性能评估Metrices详解
- Django QuerySet查询集原理及代码实例
- Python中zipfile压缩文件模块的基本使用教程
- 基于nexus3配置Python仓库过程详解
- Python Django中间件使用原理及流程分析
- keras读取h5文件load_weights、load代码操作