天天造轮子第七天 - 中间件实现 - Compose 的 N 种姿势

本文涉及的产品
云原生网关 MSE Higress,422元/月
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 天天造轮子第七天 - 中间件实现 - Compose 的 N 种姿势

造轮子计划


计划天天变


  • 框架








  • 原生Ajax


  • JS基础



  • Promise


  • Promise.all/race


  • 路由


  • new


  • [call/apply/bind](./call apply bind)


  • Object.create


  • 深拷贝、浅拷贝


  • 算法、设计模式


  • 二分查找


  • 快排


  • 二分查找


  • 冒泡排序


  • 选择排序


  • 订阅发布


  • 斐波那契算法


  • 去重


一、中间件是什么


中间件可以有两个不同的解释。


1. 传统的中间件概念


维基百科


中间件(英语:Middleware),是提供系统软件和应用软件之间连接的软件,以便于软件各部件之间的沟通,特别是应用软件对于系统软件的集中的逻辑,在现代信息技术应用框架如Web服务、面向服务的体系结构等中应用比较广泛。如数据库、Apache的Tomcat,IBM公司的WebSphere,BEA公司的WebLogic应用服务器,东方通公司的Tong系列中间件,以及Kingdee公司的等都属于中间件。


在很久很久以前,1988年5月,求伯君的普通技术人员在一个宾馆的出租房间里凭借一台386电脑写出了WPS(Word Processing System)1.0,从此开创了中文字处理时代。

那个时代写程序基本就是用C从在操作系统上直接开发


操作系统 => 业务程序


但是后来你会发现一个复杂的业务程序通常情况下可以分化为两层,可以抽离出一层程序和业务完全无关比如Web服务,数据存储等。


操作系统 =>  中间件(Middleware) => 业务程序


这个就是中间件最原始的概念。


中间件可以屏蔽操作系统底层,减少程序设计的复杂性。


操作系统(Linux) =>  中间件(Middleware) => 业务程序


操作系统(Windows) =>  中间件(Middleware) => 业务程序


操作系统(Unix SCO) =>  中间件(Middleware) => 业务程序


常见的中间件产品


  • 应用服务器 : JBoss、Tomcat、Geronimo、JOnAS


  • 消息中间件: Kafka、 RabbitMQ


  • 交易中间件: TUXEDO、TUXONE


2. JS世界中的中间件


JS世界中无论是Express、Koa、Redux中都会有中间件实现。其实这个中间件主要是为了方便的将业务逻辑拼装成一个逻辑处理链条。其实是对设计模式中责任链模式的一种实现。我们通常也会将他称为洋葱圈模型。


网络异常,图片无法展示
|


3. 为切面编程而生的


对于复杂的系统,切面编程是刚需。完成一个核心逻辑之前和之后,还是粗了错误都需要做对应的处理。


比如转账:


  • 转账前 :  鉴权  安全性 开启事务


  • 转账后: 操作日志 关闭事务


  • 异常: 账户回滚 错误日志


想象一下假设我们hard coding这个代码有多麻烦。


transfer() {
  // 鉴权
  tokenCheck()
  // 开启事务
  tx.begin()
  try {
    // 核心逻辑
    // 操作日志
    log('   xxxxxx  ')
  } cache(err) {
    // 错误日志
    log('   xxxxxx  ')
    // 回滚事务
    tx.rollback
  } finally {
    // 关闭事务
    tx.end
  }
}


其实 还没有完 ,如果你在程序中还要加上同步锁、资源池获取(比如数据库连接池)、缓存呢。


解决办法就是需要提供AOP编程的可能。


“面向切面编程”,这样的名字并不是非常容易理解,且容易产生一些误导。有些人认为“OOP/OOD11即将落伍,AOP是新一代软件开发方式”。显然,发言者并没有理解AOP的含义。Aspect,的确是“方面”的意思。不过,汉语传统语义中的“方面”,大多数情况下指的是一件事情的不同维度、或者说不同角度上的特性,比如我们常说:“这件事情要从几个方面来看待”,往往意思是:需要从不同的角度来看待同一个事物。这里的“方面”,指的是事物的外在特性在不同观察角度下的体现。而在AOP中,Aspect的含义,可能更多的理解为“切面”比较合适。


如果有切面编程,代码通常只需要在一次性在定义在所有业务前都做什么就好了


@Before(tokenCheck) // 鉴权
@Before(tx.begin) // 开启事务
@After(log) // 操作日志
@After(tx.end) // 关闭事务
@Exception(log) //错误日志
@Exception(tx.rollback) // 回滚事务
traansferBase() {
  // 业务逻辑
}


当然我这个是用Anotation的形式写的,总之就是只需要一次性定义,就可以全部执行。


满满的高内聚低耦合。


4. 洋葱圈的产生


其实为了达到AOP的目的有很多种实现方法,我们所说的洋葱圈就是一种。


其实就是责任链模式,责任链模式相当于给核心任务加上了一次层层的洋葱圈。


网络异常,图片无法展示
|


相当于将事前处理,事后处理,和业务程序编成了链条。


网络异常,图片无法展示
|


这样就可以达到切面编程的目的了。


二、需求


洋葱圈的实现需要考虑同步和异步情况。这里仅以同步情况为例。


详细的请参考代码 :github.com/su37josephx…


it('同步函数', async () => {
        const mockFn = jest.fn()
        // const mockFn = console.log
        const middlewares = [
            next => {
                mockFn('1 start')
                next()
                mockFn('1 end')
            },
            next => {
                mockFn('2 start')
                next()
                mockFn('2 end')
            }
        ]
        const func = compose(middlewares)
        viewLog && console.log('同步函数 > compose定义', compose.toString());
        func();
        const calls = mockFn.mock.calls
        viewLog && console.log('第一次', calls);
        expect(calls.length).toBe(4);
        expect(calls[0][0]).toBe('1 start');
        expect(calls[1][0]).toBe('2 start');
        expect(calls[2][0]).toBe('2 end');
        expect(calls[3][0]).toBe('1 end');
    })


三、功能实现


其实洋葱圈要是写可以写个十几种。以下代码都是我的学生的作业。


No1:express 递归实现


No2:Koa 递归实现


No3:Koa Reduce实现


No4:Koa Class实现


No5:Redux Reduce实现


No6:Redux ReduceRight实现


No7:Redux ReduceRight Promise实现


No8:Chain of Responsibility Pattern 责任链模式实现


No9:[List 通过列表递归实现](


我们就随便说几个,还有很多,欢迎大家PR 。还有哪些我后面会说。


1. Express递归实现


module.exports.compose = (middlewares = []) => {
    if (!Array.isArray(middlewares)) {
        middlewares = Array.from(arguments);
    }
    if (middlewares.some(fn => typeof fn !== 'function')) {
        throw new TypeError('Middleware must be composed of functions!');
    }
    return async () => {
        let idx = 0;
        async function next() {
            if (idx === middlewares.length) {
                return Promise.resolve();
            }
            if (idx < middlewares.length) {
                return Promise.resolve(middlewares[idx++](next));
            }
        }
        return await next();
    };
};


2.Koa递归实现


module.exports.compose = (middlewares = []) => {
    if (!Array.isArray(middlewares)) {
        middlewares = Array.from(arguments);
    }
    if (middlewares.some(fn => typeof fn !== 'function')) {
        throw new TypeError('Middleware must be composed of functions!');
    }
    return function () {
        return dispatch(0);
        function dispatch(i) {
            let fn = middlewares[i];
            if (!fn) {
                return Promise.resolve();
            }
            return Promise.resolve(
                fn(function next() {
                    return dispatch(i + 1);
                })
            );
        }
    };
};


3. Reduce实现


module.exports.compose = (middlewares = []) => {
    if (!Array.isArray(middlewares)) {
        middlewares = Array.from(arguments);
    }
    if (middlewares.length === 0) {
        return arg => arg;
    }
    if (middlewares.some(fn => typeof fn !== 'function')) {
        throw new TypeError('Middleware must be composed of functions!');
    }
    return (next = async () => {}) => middlewares.reduce((a, b) => arg => a(() => b(arg)))(next);
};


网络异常,图片无法展示
|


四、升华扩展


其实 就是责任链的实现,欢迎大家PR写完了保证你进入另外一个变成世界


blog.csdn.net/liuwenzhe20…


请参考这篇大哥雄文


  • OOP风格


归纳一下


  • 面向对象风格OOP


  • 解法一 用模板方法模式实现责任链模式


  • 解法二 用策略模式实现责任链模式


  • 函数式风格Functional Programming


  • 解法三 用一等公民函数替换策略模式实现责任链模式


  • 解法四 用偏应用函数实现责任链模式


  • 解法五 用偏函数实现责任链模式


  • 响应式风格Reactive Programming


  • 解法六 用Actor模型实现责任链模式


  • 解法七 用RXReactive eXtension响应式扩展实现责任链模式


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
消息中间件 JSON 中间件
你用Go写过中间件吗?带你用Go实现【操作日志中间件】
管理后台所有修改,添加,删除的操作都要记录;操作日志的统计不影响主程序的性能
562 9
你用Go写过中间件吗?带你用Go实现【操作日志中间件】
|
中间件 uml
阿里中间件seata源码剖析六:TCC模式中2阶段提交实现
阿里中间件seata源码剖析六:TCC模式中2阶段提交实现
384 6
阿里中间件seata源码剖析六:TCC模式中2阶段提交实现
|
中间件 程序员 Go
你用Go写过中间件吗?带你用Gin实现【用户角色权限管理中间件】
管理后台有超管权限,超管拥有所有权限;普通管理员可以设置角色,角色单选;角色可以赋予多个权限,权限多选;这样我们就实现了对普通管理员的角色和权限的灵活管理
346 7
你用Go写过中间件吗?带你用Gin实现【用户角色权限管理中间件】
|
中间件 Python
Python编程:Django中间件实现登陆验证
Python编程:Django中间件实现登陆验证
191 5
Python编程:Django中间件实现登陆验证
|
中间件 Java Spring
从-1开始实现一个中间件
别人都写从0开始实现xxx,我先从-1开始就显得更牛逼一些。 今天,先开个头,来教大家怎么实现一个中间件。
183 6
从-1开始实现一个中间件
|
JavaScript 中间件
两分钟搞懂从函数组合到中间件实现
很多JS框架中都会用到中间件,形成一个洋葱圈结构,这样可以很好的应付业务逻辑中的对于切面描述的需要。 经典案例比如Redux 中间件和Koa 中间件
205 2
|
存储 分布式计算 监控
阿里云互联网中间件:让企业实现业务云化持续创新|学习笔记
快速学习 阿里云互联网中间件:让企业实现业务云化持续创新
216 7
|
编解码 运维 监控
电商直播平台如何借助容器与中间件实现研发效率提升100%?
经过实际场景验证及用户的综合评估,电商直播平台借助全面的云原生容器化能力和中间件产品能力,大幅提升开发部署运维效率达50%~100%,极大地提升了用户体验,为业务持续发展打下了坚实的基础。
7256 5
电商直播平台如何借助容器与中间件实现研发效率提升100%?
|
存储 JavaScript NoSQL
SpringBoot2 整合MinIO中间件,实现文件便捷管理
MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
771 7
SpringBoot2 整合MinIO中间件,实现文件便捷管理
|
消息中间件 Prometheus Cloud Native
阿里云原生中间件首次实现自研、开源、商用“三位一体”,技术飞轮效应显现
阿里云在探索中一直存在的苦恼,是内部的自研体系、商业化的产品技术与开源的项目,三方的技术路线一直没有机会融为一体。然而,就在今年阿里云提出了“三位一体”理念,即将“自研技术”、“开源项目”、“商业产品”形成统一的技术体系,最大化技术的价值。
阿里云原生中间件首次实现自研、开源、商用“三位一体”,技术飞轮效应显现