Koa洋葱模型与面向切面编程(AOP)

Posted by csy on 2019-05-26

前言 - 什么是AOP

AOP又叫面向切面编程,是一种非侵入式扩充对象、方法和函数行为的技术。什么是非侵入式呢?侵入式是需要知道框架中的代码,与框架代码紧密结合在一起。非侵入式是可以自由选择和组装各个功能模块,没有过多的依赖。有很多方式可以增加和合并方法,例如继承、组合、委托。通过AOP可以从“外部”去增加一些行为,在很多情况下,AOP比其他方式更灵活、更少侵入。简而言之,AOP就是在现有代码程序中,在程序生命周期或者横向流程中 加入/减去 一个或多个功能,不影响原有功能。

面向切面编程(AOP)

假设在代码中有一个“简单类”,在程序中许多地方都使用了这个类的实例,我们怀疑这个类中的一个方法是导致性能问题的根源,想要跟踪记录该方法的输入参数、计算所花费的时间,以及返回的result的值。 有几种方式:

  1. 修改所有调用点
    在被调用的每个位置都进行日志记录,但方法在很多位置被调用,这意味着将进行很多次的复制、粘贴。可能会遗漏某些位置,更糟糕的是,在收集到数据后,可能忘记将某些位置的代码移除。
  2. 修改源代码
    尽管这样做只需要修改一个位置,但具有相当的入侵性:需要修改类的源代码。想象一下,如果类中方法的源码比较复杂,包含多处 return 和一些 try/catch/finally 块。在不改变方法的行为的同时,收集到所需要的数据,代码修改起来并不是那么容易。
  3. 使用继承
    可以避免修改类的源代码,但是需要将使用类的每一个位置都修改为继承类。

关注分离

“跟踪记录”这个分析功能,它与“简单类”原本的目的无关,它是一个额外的功能。“简单类”很可能是为了解决特定领域的特定问题而创建,上面那些解决方案都将一些无关行为引入到“简单类”中。“简单类”只需要完成自身的工作而不需要关心任何与分析相关的工作,但以上的解决方案都迫使分析工作与 “简单类” 的本质工作直接关联在一起。 我们需要一种技术,以一种可控的、非侵入式的方式来引入这类行为。也就是说,这种方式能够有力保障 “简单类” 的行为,并且不需要我们修改 “简单类” 的源代码,或者修改使用 “简单类” 的位置的源代码。

AOP的应用

在 JavaScript,这非常简单,甚至不需要使用任何工具或库就能实现 AOP,但它非常实用,就像其他工具或库帮助你构建可复用的模式一样。详细了解AOP可以看下这两篇文章面向切面编程(AOP)简介用AOP改善javascript代码

koa 源码

1
2
3
4
5
6
├── lib
│   ├── application.js
│   ├── context.js
│   ├── request.js
│   └── response.js
└── package.json

这个就是 GitHub https://github.com/koajs/koa上开源的koa2源码的源文件结构,核心代码就是lib目录下的四个文件

  • application.js 是整个koa2 的入口文件,封装了context,request,response,以及最核心的中间件处理流程。
  • context.js 处理应用上下文,里面直接封装部分request.js和response.js的方法
  • request.js 处理http请求
  • response.js 处理http响应

Koa.js 作为一个web框架,总结出来提供了两种能力

  • 中间件机制(AOP切面)
  • HTTP服务

Koa.js 的AOP设计

koa Koa洋葱模型本质是AOP面向切面编程,切面由中间件机制实现,中间件单一职责,遵循先进后出的切面执行顺序,类似入栈出栈的顺序。

HTTP 切面流程(请求=>中间件=>响应) 从HTTP请求拿到想要的数据,然后处理想要处理的事情,最后返回处理后的结果

Koa.js 中间件的分类

狭义中间件

  • 中间件内操作请求 request
    • 请求拦截
  • 中间件内操作响应 response
    • 响应拦截
  • 中间件内操作上下文 context
    • 直接上下文代理,初始化实例时候挂载代理在app.context上,初始化应用的时候才注入,只注入一次,每次请求都可以使用
    • 请求过程上下文代理,请求时候挂载代理在ctx上,直接被app.use,请求时候才有注入,每次请求的注入都不同
  • 大多数直接被 app.use() 加载
    • 注意: 初始化实例挂载代理context不被app.use()

举个栗子 例如 中间件koa-static主要是靠拦截请求和响应,加载静态资源,中间件koa-bodyparser主要是拦截请求后解析出HTTP请求体重的POST数据,再挂载到ctx上。

广义中间件

  • 不直接提供中间件
  • 通过间接方式提供了中间件或者子中间件
  • 间接被 app.use() 加载
  • 其他方式接入Koa切面

举个例子 例如中间koa-router 是先注册路由后形成多个子中间件,后面再封装成一个父中间件提供给app.use()加载,让所有子中间件加载到Koa.js的请求洋葱模型中。

参考文章

AOP 面向切面编程