前言

之前在公司内部做过「扩展性设计」的分享。后面重新整理形成博客形式记录自己的学习。

由于作者长期从事Web前端领域的工作的原因,本文也是基于这些技术领域发出的一些关于应用软件方向上的扩展性总结

什么是扩展性设计

软件架构设计里比较关注的几个要素:扩展性稳定性可维护性。很多领域里都会有这方面的考虑。比如:业务架构,产品架构,软件技术架构。那么具体怎么定义呢

Extensibility is a software engineering and systems design principle where the implementation takes future growth into consideration

扩展性是在考虑未来增长发展时所做的一些工程实践和系统设计原则

引用自维基百科的定义

基础理论

这里讨论的理论基础是比较原始的,类似于数学或物理中的定律定理。实践中是要结合具体场景通过组合这里理论,以及基于这些理论做些推导来形成最佳的设计

  • 找到变化的东西
  • solid原则
  • 分层明确

常用形式

中间件

中间件是一种实践比较成熟的形式了。基本的思想是根据一些约定拦截输入,做一些逻辑,或者修改挂载上下文,然后继续向下流。具体的形式上有

  • 洋葱模型中间件: koa2redux
  • 管道化模型的中间件:pipe

中间件实践中要考虑的因素是:

  • 中间件收集方式
  • 中间件之间的关系,顺序等

插件

这种形式也比较常见。类似于微内核 + plugin/addon 模型。市面上也存在了很多基于此架构的工具或框架。此模式实践中要考虑的因素是:

  • 隔离性:暴露主应用的那些能力,防止插件的运行影响主应用逻辑
  • 性能:进程模型设计,是否独立进程运行
  • 生命周期:插件加载时机,插件执行时机
  • 插件之间管理:插件之间是否能互相调用,互相影响

插件机制思想很简单,重要的是各种实现细节,实践中有不同形式与细节。这里日后单独写一篇文章来分析

配置

读取配置文件,读取参数都是这种形式。是一种简单但实用的形式。不过多讨论

案例分析

webpack中的扩展性设计

webpack 是一个比较流行的打包工具。其功能的强大,生态的繁荣离不他的插件体系和loader体系。本身作为一个流程控制中心,很多功能都是分散在各个插件里来做的

loader体系

针对特定的文件类型来做处理的,有点类似上一节提到的管道化中间件模型。针对特定类型文件可以提供多个,按照顺序管道化的处理转换。

插件体系

webpack 内部主要的两个概念是 complier主要负责构建整体流程等,compliton主要负责构建里的具体编译工作。这两者都是通过 Tapable 库来完成内部生命周期暴露,Tapable 大体上是一种 sub-pub 模式的实现, 其核心概念 hook 可以与 event系统里的某个 event 等价。在同一个hook下可以绑定很多handler的注册,有点像AOP编程思想。

一个插件的demo

function HelloWorldPlugin(options) {
    // 使用 options 设置插件实例……
}

HelloWorldPlugin.prototype.apply = function(compiler) {
    compiler.plugin('done', function() {
        console.log('Hello World!');
    });
};

module.exports = HelloWorldPlugin;

解释: 插件是约定实现带有 apply 方法的类(demo中通过构造函数和原型的方式实现)。apply 方法是 webpack 内部使用的。webpack 暴露了 complier 对象给 apply,可以使用其暴露的生命周期钩子来处理所要逻辑,完整的钩子列表可以在官网上找

babel

副作用

对,最大的副作用就是过度设计,无论何种设计都不是免费的。完美的扩展机制是需要考虑很多因素的,上文有一些分析。倘若为了尚不明确的问题做复杂的设计,可能得不偿失。

总结

扩展性很重要,同时也要警惕副作用。切不可为了设计而设计,实用有效,能为公司真正带来价值的才能成为好设计。当然如果职位就是探索研究型的,可以忽略一些副作用

个人觉得做架构的最佳实践:充分理解问题的场景,规模,特有的属性等,未来的规划,结合基础理论来做具体设计。对未来的增长不明确时,不建议花费很大成本做这些设计。