中台扩展性设计(四): 扩展点插件化框架落地

1,636 阅读5分钟

系列文章:

一、前言

通过前三篇文章的介绍,我们了解到了系统腐化的原因和过程,以及一种提高系统扩展性的设计思想。本文将参考淘宝的TMF框架的思想,落地一套扩展点框架,为了体现框架提供的核心功能,因此起名为easy-extension

项目地址: github.com/xiaoshicae/…

二、easy-extension框架期望实现的目标效果

以spring-web项目为例,扩展点会根据本次请求命中的业务,动态执行扩展点实现。实现的效果如下 :

@RestController
@RequestMapping("/api")
public class Controller {
    // 动态注入扩展点,不同业务会有不同实现,业务挂在的能力也会有不同的实现
    // 最终会根据业务及能力叠加,注入优先级最高的实现
    @Autowired
    private Extension ext; // 此处注入的是扩展点的动态代理

    @RequestMapping("/process")
    public String process() {
        // 动态执行扩展点的实现,根据命中的业务,生效的能力,执行优先级最高的实现
        // 如果命中的业务和挂载的能力都没实现该扩展点,系统会提供默认实现进行兜底
        String res = ext.doSomething();
        return res;
    }
}

// 扩展点定义
interface Extension {
    String doSomething();
}

回答一下上一篇留下的问题,如果命中的业务和挂载的能力均没有实现该扩展点怎么处理 ?答案是:系统会为每个扩展点提供一个默认兜底实现)。

三、easy-extension框架运行大致流程图

  • 框架启动流程:扫描所有的扩展点定义,业务,能力,默认扩展点实现到spring容器,自动装配扩展点的动态代理。

  • 请求处理流程:初始化session,匹配业务和能力,按优先级执行扩展点的实现,请求结束前需要清理下session。

四、easy-extension框架使用详细步骤

完整的框架使用流程的demo地址,可以参考: github.com/xiaoshicae/…

  1. maven引入
<dependency>
    <groupId>io.github.xiaoshicae</groupId>
    <artifactId>easy-extension-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>
  1. 扩展点定义
// 扩展点,需要@ExtensionPoint注解,以便spring包扫描识别
@ExtensionPoint
public interface Extension1 {
    String doSomething1();
}

@ExtensionPoint
public interface Extension2 {
    String doSomething2();
}
  1. 定义用于身份和能力匹配的参数结构
// 用于业务或者能力匹配的参数
public class MyParam {
    private final String name;

    public MyParam(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
  1. 定义默认能力(匹配不到任何实现时,默认兜底实现)
// 默认能力,必须实现所有@ExtensionPoint注解标注的扩展点
// 业务没有实现某个扩展点时,默认能力作为兜底实现
// 需要@Component注解,以便spring包扫描识别
@Component
public class DefaultAbility extends BaseDefaultAbility<MyParam> implements Extension1, Extension2 {
    @Override
    public String doSomething1() {     // 默认兜底的能力必须实现所有的扩展点
        return "default doSomething1";
    }

    @Override
    public String doSomething2() {    // 默认兜底的能力必须实现所有的扩展点
        return "default doSomething2";
    }
}
  1. 定义能力
// 能力X,实现了扩展点
// 需要@Component注解,以便spring包扫描识别
@Component
public class AbilityX extends AbstractAbility<MyParam> implements Extension1 {
    // 能力X的code
    @Override
    public String code() {
        return "app.ability.x";
    }

    // 能力X生效条件
    @Override
    public Boolean match(MyParam param) {
        return param != null && param.getName().equals("x");
    }

    // 能力X实现了扩展点
    @Override
    public String doSomething1() {
        return "abilityX doSomething1";
    }
}
  1. 定义业务 & 挂载能力
// 业务A,实现了扩展点1和扩展点2
// 需要@Component注解,以便spring包扫描识别
@Component
public class BusinessA extends AbstractBusiness<MyParam> implements Extension1, Extension2 {
    // 业务A身份唯一标识
    @Override
    public String code() {
        return "app.business.a";
    }

    // 命中业务A的生效条件
    @Override
    public Boolean match(MyParam param) {
        return param != null && param.getName().equals("a");
    }

    // 优先级(复杂场景下,业务挂载了能力,可能存在扩展点冲突,需要通过优先级解决冲突)
    @Override
    public Integer priority() {
        return 100;
    }

    // 业务A挂载了能力X,且优先级50,高于自身的优先级100 (UsedAbility参考后面的定义)
    @Override
    public List<UsedAbility> usedAbilities() {
        return List.of(new UsedAbility("app.ability.x", 50));
    }

    // 业务A实现了扩展点1
    @Override
    public String doSomething1() {
        return "businessA doSomething1";
    }
    
    // 业务A实现了扩展点2
    @Override
    public String doSomething2() {
        return "businessA doSomething2";
    }
}

class UsedAbility {
    private String  code;
    private Integer priority;

    public UsedAbility(String code, Integer priority) {
        this.code = code;
        this.priority = priority;
    }
}
  1. 定义interceptor,在每次请求时,注入用于身份和能力匹配的参数
@Component
public class Interceptor implements HandlerInterceptor {

    // 框架启动会自动注入ISessionManager,可以直接通过spring自动注入获取
    @Resource
    private ISessionManager<MyParam> sessionManager;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 根据入参构造用于business和ability匹配的参数
        String name = request.getParameter("name") != null ? request.getParameter("name").trim() : "unknown";

        // 初始化session
        sessionManager.initSession(new MyParam(name));
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //  清空session
        sessionManager.removeSession();
    }
}


// 注入Interceptor
@Configuration
public class WebInterceptorConfigurer implements WebMvcConfigurer {
    @Resource
    private Interceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns("/favicon.ico");
    }
} 

四、小结

本文介绍了基于淘宝的TMF框架的思想,落地一套扩展点框架。这套思想最大的优势是,实现了通用代码和扩展代码的分离,中台只沉淀通用的交易流程,扩展功能下放到了业务进行实现,实现了系统关注点的分离。下篇文章将会讨论下,这套框架适用场景,可能碰到的潜在问题,以及一些迭代优化方向。