使用koa2+es6/7打造高质量Restful API

如今nodejs变得越来越火热,采用nodejs实现前后端分离架构已被多数大公司所采用。

下面,我们来探讨下,如何使用koa2+es6/7来打造高质量的Restful风格API。

刨根问底,篇幅略长,精华在后面,需要耐心看。

1. 两种模式

一种是耦合模式,即接口层和逻辑层都由一个函数来处理完成。

另一种是分离模式,即接口层和逻辑层是分开的。

下面我们先来说第一种。

先举个粟子,以express为例:

这种在过去很常见,相信很多人都写过,我也不例外。但并不推荐。

首先,一个应用的api通常会很多,如果应用够复杂,那意味着你的api可能要处理非常多的逻辑。

而为了应付这些需求,你不得不建立很多的文件,甚至困扰于如何划分和组织好这些文件。

其次,后期并不好维护,当api过多,过于繁杂时,文件深层嵌套,也许你找一个api文件都费神费力。

同样先来个粟子:

很显然,这种api已将接口层和逻辑层分离了,接口层由一个router.js文件来统一定义,而每个接口的逻辑层则由单独的文件来处理,并按不同功能模块用不同文件夹来组织这些逻辑文件。

那么,这样做有什么好处呢?

首先,很直观,整个结构很清晰,一目了然

其次,只需要你专注于处理逻辑

再者,api集中在router.js文件定义,同事更容易看懂你的代码结构,或者知道你增改了哪些api等等,这很方便于多人协同开发,在大型开发中尤为重要

很显然,分离模式优于耦合模式。

2. 如何更好地组织逻辑层

经过上面的分析之后,我们选择更优的分离模式, 它只需要你关注逻辑层。

但是,以上面分离模式的例子为例,每一个接口仍然需要单独一个js文件来处理它的逻辑层,并且需要用很多不同文件夹来组织它
们,假如应用足够大,有几十甚至上百个api,那意味着很有可能你的js逻辑文件也达几十乃至上百个,而用来划分和组织这些js文
件的文件夹也不在少数。

这就造成了过于臃肿,难以维护的毛病。

那么,有没有可能,一个功能模块只需要一个js文件来处理它们的所有逻辑层,并更具可维护性呢?

打个比方,现在有一个博客站点,我仅使用一个user.js文件来处理用户模块所有api的逻辑层,包括注册,登录,修改,删除,密码重置等等,另外用一个article.js文件来处理文章模块所有api的逻辑层,包括发布,修改,获取详情,点赞,评论,删除等等。

如果可以做到这样,那就意味着代码量大大减少,且可维护性更高。

而要做到这步,我们需要解决两个问题,一个是异步回调,因为异步回调使我们增加了很多代码量,逻辑复杂,二是如何批量定义和导出大量api的逻辑层方法。

首先,我们先来解决异步回调这个问题,下面将会展开讲解。

为了减少篇幅,下面只做简要的浅析。

我们先来回顾一下历史。

鉴于nodejs的回调机制,很多异步操作都需要回调来完成,如果你的逻辑足够复杂,很可能就会陷进回调地狱,下面是一个简单的例子:

同样,express也不例外,常常会让你深陷回调地狱。通常一个api需要写大量的代码来完成,此时为了更好地开发和维护,你不得不每个api都单独一个js文件来处理。

为了解决异步回调这个大问题,js生态出现了很多解决方案,

其中比较好的两个——promise,async。

这曾是一个非常优秀的第三方模块,它基于回调机制来实现,是处理异步回调很好的解决方案,如今github上已超两万多颗星。

下面来个粟子:

很显然,这很大程度上避免了回调地狱,并且有一个完整的控制流,使你可以很好的组织代码。

首先,promise是es6的特性之一,实际是可用来传递异步操作流的对象。

promise提供三种状态,Pending(进行中),Resolved(已解决),Rejected(已失败)。

promise提供两个方法,resolve()和reject(),可用于处理最终结果。

promise还提供一个then方法,用于异步处理过程,这是一个控制流方法,可以不停地执行下去,直到得到你想要的结果。

promise还提供了catch方法,用于捕获和处理异步处理过程中出现的异常。

下面来举个粟子:

那么,能不能同时执行多个promise实例呢?

可以的,promise.all()方法可以帮到你。

不得不说,promise是解决异步回调的一大进步,是一个非常优秀的解决方案。而由于promise的强大,生态圈出现了很多基于promise的优秀模块, 比如bluebird, 等等。

然而,promise并非终点,它只是弱化了回调地狱,并不能真正消除回调。使用promise仍然要处理很多复杂的逻辑,以及写很多的逻辑代码

而要消除回调,意味着要实现以同步的方式来写异步编程。

那么如何来实现?

和promise一样,generator同样是es6的新特性,但它并非为解决回调而存在的,只是它恰好拥有这个能力,而TJ大神看到这种可能,于是他利用generator封装了co。并基于co,他又创造了个更轻量,性能更好的koa1web框架。

自此,koa1终于诞生了!它迎合了es6和co,

koa1和express相比,有非常大的进步,其中之一就是它很大程度上真正地解决了异步回调问题,真正意义上实现同步方式来写异步编程。

再就是,koa1更轻量,性能比express更为优异。

下面继续来个举个粟子:

看完这个粟子,是不是十分的激动呢?

我们再来看看,基于co的koa1是如何处理异步的, 同样举个粟子:

想象一下,这个例子如果使用express来做会是怎样呢?

相信你心中有数,很无情地抛弃了express,express哭晕厕所😢。

下面开始回归正题。

我们来探讨下,如何使用更好的组织结构,更少的代码量来实现大量api的逻辑层

3, 探讨一,koa1的实现

经过前面的诸多讲述了解到,异步回调这一大难题,到了koa1才真正意义上的得以解决,准确来说是generator的功劳。

以同步的方式处理异步回调,这才是我们想要的结果,意味着我们可以用很少的代码量来实现api的逻辑层。

解决了异步回调后,此时我们考虑另一个问题,如何集中处理,暴露大量api逻辑层?

此时,时代进步的利器——es6,排上用场了。

这里主要使用es6的几个新特效,export, import等等。

下面,我们举个粟子来讲述:

首先是api接口层

不知注意到没,用户模块和文章模块都分别只引入了一个文件,分别是userctrl.js和articlectrl.js,所有用户和文章模块相关的api逻辑层都集中在这两个文件中处理。

如何做的呢? 请看下面的粟子:

  • 用户模块

  • 文章模块

到了这一步,api接口层和逻辑层都已处理完毕。

对于这个问题,我们可以把try/catch封装成一个中间件来处理,只需要把这个中间放在路由之前执行即可。对此,可以参考阿里云栖里的这篇文章——如何优雅的在 koa 中处理错误

至此,一个基于koa1+es6的Restful API打造完成。

然而,这仍不是终点。

4. co的末日,也是koa1的末日

co/koa1这么厉害,实现了promise,async都解决不了的同步写异步,为什么会是末日呢?

co/koa1并不是不好,而是有比它更好的,从而淹没了他们的光芒,所谓壮士一去不复返,垂泪三千尺😢。

async/await来势汹汹,它有个代号,叫——终结者。别误会,不是那个酷酷的美国大叔。

async/await并非第三方实现,而是原生javascript的实现,也就是说它不是bluebird,q,async那一流,将来它是要进入w3c标准的,官方的解决方案。 准确地说,它才是正统皇帝,generator只是代皇帝,bluebird,q,async之类的则只是江湖侠客。

为此,自nodejs发布到7.x以后,TJ 大神推出了koa2,内置co包,直接支持async/await。并将会在koa3中完全移除对generators的支持。

async/await非常新,它并不属于es6,而是属于es7。和generator一样,它实现了同步写异步,终结异步回调。

而async/await具有非常大的优势,首先它本身是generator语法糖,自带执行器,更具语义化,适用性更广。其次,它并不需要像co这样的第三方实现,而是原生支持的。

5. 探讨二,koa2+es6/7的实现

直接奔入最终主题。

前面讲了koa1+es6实现Restful API的打造,可它并非是最优解。

真正的最优方案是koa2+async/await+class的实现。

这里为什么提到class呢?

class是es6版的面向对象的实现,是的,你没有看错,你曾经所熟悉的oop可以玩起来了。

可是,这里为什么需要用到它?

因为,class+async/await的结合,可以使你更好的组织api的逻辑层,语义更清晰,结构更清晰,代码量更少更轻,更容易维护。至此,你不再需要export每个接口逻辑了。另一个优点,它同样具有很好的性能。

首先是接口层:

然后是逻辑层

是不是更清晰,更有结构性了呢?

你甚至还可以用extends(继承)来实现更复杂的api。

但是,不知你有没有注意到一个细节,上面的例子用了new实例化。

实例化,意味着会消耗一定内存,消耗性能。虽然在后端这是种消耗不会很大。

但是作为一名优秀的程序员,我们尽量追求极致。

需要明白的一点是,通常我们的api不会复杂到大量使用oop的知识,比如大量地使用原型,继承来实现复杂的实例,并没有,至少后端js逻辑不会是前端那般复杂。

其次,我们的需求很简单,只需要能够批量定义和导出众多api的逻辑层方法即可。

既然如此,为什么不用静态方法呢?是的,static来了。

es6的class中,可用static来定义静态方法,甚至可以定义静态属性(es7才实现)。静态方法并不需要实例化就可以访问,也就意味着,使用static,你不需要new,你可以减少内存的损耗。

下面我们改造一下上面逻辑层的例子:

是不是感觉高大上了很多?

另外,还有两点我们可以优化的。

第一点是,避免在每个接口逻辑层中使用try/catch,而是封装一个try/catch中间件来处理它们,这样可以减少代码量,工作量,以及减少空间的占用。

第二点是,把一些公共方法抽离出来,同样用class来组织它们,使用也很简单,你可以单独引进,也可以使用extends来继承公共方法的class类,以访问父类方法的方式来获取它们。

至此,一个基于koa2+es6/7打造的高质量Restful API终于完成。

如果你正准备学nodejs,除了原生node以外,你可以直接学习和使用koa2。

如果你习惯于express或koa1,也建议迁移到koa2

async/await以及众多es6/7特性的出现,是对nodejs负担的一种释放,你可以很好地利用好它们来提高你的编码效率和质量。

原文:https://zhuanlan.zhihu.com/p/26216336

Original: https://www.cnblogs.com/klsw/p/9220729.html
Author: 快乐地编程
Title: 使用koa2+es6/7打造高质量Restful API

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/617128/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球