程序员的保命技能——插件扩展点引擎,你必须要了解~
点击关注公众号,“技术干货”及时达!
业务中台要接入很多的业务方,每个业务方并不是完全相同。很多时候无法完全复用,需要改造系统适应新的业务。
新增业务代码时,务必要保证原有业务不受影响,如果没有插件扩展点能力,就会充斥大量的 if else 。
if (biz == BizA || biz == BizB) { //do some thing //这部分逻辑相同 if (biz == BizA) { //差异化处理 } if(biz == BizB) { //差异化逻辑 }}例如上面的代码,不同的业务线若有差异化逻辑,需要新增分支单独处理。想象一下,当有 10 多个业务接入了你的系统,那么一定让人抓狂……
任何一个人都无法保证对 10 多种业务完全熟悉,每个人可能只负责 1 个业务,然而如果没有代码逻辑的隔离,维护者只能在千丝万缕中,才能找到目标代码逻辑。更可怕的是,每次新增一个业务,需要在原有的屎山中继续??,不断新增 if else。直到有一天,有一个倒霉蛋改错了代码,导致其他重要业务受影响,引发线上故障。
想象一下,当你改了几行代码以后,要求测试同学,回归10 多个业务线的全部逻辑?这显然不现实。
以上的问题和痛点可归纳为:代码隔离性和业务扩展点问题。解决这两类问题有如下手段!
「使用流程引擎,为不同的业务配置不同的流程执行链」「使用插件扩展引擎,不同的业务实现差异化部分。」MemberClub 中大量使用流程引擎和插件扩展引擎解决业务隔离性和扩展性 问题。
MemberClub是托管在Gitee平台的开源项目,提供了付费会员的交易解决方案,在各类购买场景下提供各类会员形态的履约及售后结算能力,具体介绍可参见 https://gitee.com/juejinwuyang/memberclub
在 程序员的保命技能——流程编排,你一定要了解!文章中,我介绍了流程引擎的设计原理,本篇文章我们分析 扩展点引擎设计。
从以下几个方面了解:扩展点接口的定义、扩展点实现类的定义、加载扩展点地图、引用和调用扩展点
定义扩展点如下接口 PurchaseExtension 抽象了购买域 提交订单和取消订单接口,各产品线提供各自的实现类。实现类要添加 ExtensionProvider 注解,该注解声明了适用的业务线和业务场景。接口实现逻辑中共执行哪些流程。在 submit/cancel接口中 执行流程链。
扩展点接口定义@ExtensionConfig(desc = "购买流程扩展点", type = ExtensionType.PURCHASE, must = true)public interface PurchaseExtension extends BaseExtension {
public void submit(PurchaseSubmitContext context);// 提交订单
public void reverse(AfterSaleApplyContext context);//售后逆向
public void cancel(PurchaseCancelContext context);// 取消订单}ExtensionProvider 注解该注解集成了 Service 注解,声明该注解会被加载进 Spring 上下文。同时注解信息包括业务线和业务场景值。
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Servicepublic @interface ExtensionProvider {
public Route[] bizScenes();
public String desc();}扩展点实现类
加载扩展点ExtensionManage 类在 Spring 启动阶段,从 ApplicationContext 上下文加载 有 ExtensionProvider 注解的修饰的 Bean。注解上声明了 适用的业务线和业务域,并且将以上信息 映射到 Table 中。Table 类是 guava 提供的类似于 HashMap 的工具类,和 Map 不同的是,获取 Table 中的 value 需要 key 和 subKey 两层映射。
在 Table 中,两种映射分别是业务线和 业务场景。
@Getterprivate TableBizTypeEnum, String, ListObject bizExtensionMeta = HashBasedTable.create();
@PostConstructpublic void init() { String[] beanNames = context.getBeanNamesForAnnotation(ExtensionProvider.class);
for (String beanName : beanNames) { Object bean = context.getBean(beanName); SetClass interfaces = ClassUtils.getAllInterfacesForClassAsSet(bean.getClass()); ExtensionProvider extension = AnnotationUtils.findAnnotation(bean.getClass(), ExtensionProvider.class); Route[] routes = extension.bizScenes();
for (Class anInterface : interfaces) { if (BaseExtension.class.isAssignableFrom(anInterface)) { for (Route route : routes) { for (SceneEnum scene : route.scenes()) { String key = buildKey(anInterface, route.bizType().getCode(), scene.getValue());
Object value = extensionBeanMap.put(key, bean); if (value != null) { CommonLog.error("注册 Extension key:{}冲突", key); throw new RuntimeException("注册 Extension 冲突"); } CommonLog.info("注册 Extension key:{}, 接口:{}, 实现类:{}", key, anInterface.getSimpleName(), bean.getClass().getSimpleName());
ListObject extensions = bizExtensionMeta.get(route.bizType(), anInterface.getSimpleName()); if (extensions == null) { bizExtensionMeta.put(route.bizType(), anInterface.getSimpleName(), Lists.newArrayList(bean)); } } } } } }}
private String buildKey(Class anInterface, int bizType, String scene) { String key = String.format("%s_%s_%s", anInterface.getSimpleName(), bizType, scene); return key;}以上代码地址在:Git 地址
引用扩展点可通过 ExtensionManager.getExtension 方法引用扩展点。如下提单接口代码展示了 如何获取 PurchaseExtension 的实现类。
PurchaseExtension extension = extensionManager.getExtension(context.toDefaultBizScene(),PurchaseExtension.class);extension.submit(context);getExtension 方法中 将通过 产品线和产品域及 接口类,获取到实现类。
public T getExtension(BizScene bizScene, Class tClass) { if (!tClass.isInterface()) { throw new RuntimeException(String.format("%s 需要是一个接口", tClass.getSimpleName())); } if (!BaseExtension.class.isAssignableFrom(tClass)) { throw new RuntimeException(String.format("%s 需要继承 BaseExtension 接口", tClass.getSimpleName())); }
String key = buildKey(tClass, bizScene.getBizType(), bizScene.getScene()); T value = (T) extensionBeanMap.get(key);
if (value == null) { key = buildKey(tClass, BizTypeEnum.DEFAULT.getCode(), SceneEnum.DEFAULT_SCENE.getValue()); value = (T) extensionBeanMap.get(key); }
if (value == null) { throw new RuntimeException(String.format("%s 没有找到实现类%s", tClass.getSimpleName(), bizScene.getKey())); } return value;}最后MemberClub 中大量使用流程引擎和插件扩展引擎解决业务隔离性和扩展性 问题,以上代码均可以在 MemberClub项目中找到。代码地址:Git地址
点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线