Nest.js和koa.js有什么不一样?
1、应用场景
2、团队背景
3、未来的发展
等等...
node 里面编写服务器最终都是调用 node 原生的 http 模块,一个简单的演示服务器:
const http = require('http');
const server = http.createServer((req, res) => {
console.log('接收到一次请求!');
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h2>response content</h2>');
});
server.listen(8888, '127.0.0.1', () => {
console.log('服务器启动了!');
});
/**
运行并请求 http://127.0.0.1:8888/
控制台输出:
服务器启动了!
接收到一次请求!
*/
也就是通过 http.createServer 构造一个 server 对象,它接受一个回调函数处理每次的 http 请求,通过 listen 函数指定启动参数并且启动 server,和 java 的 servlet 一样简单的流程:请求,处理,相应。
koa 其实就是对原生的 http 模块做了简单的封装,实现中间件机制,和洋葱模型的调用顺序,使用 koa 编写一个简单的演示服务器:
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('接收到一次请求!');
console.log('我是中间件1!')
await next();
})
app.use(async (ctx, next) => {
console.log('我是中间件2!')
ctx.response.status = 200;
ctx.response.body = '<h2>response content</h2>';
await next();
})
const nativeServerObj = app.listen(8888, '127.0.0.1', () => {
console.log('服务器启动了!')
})
/**
运行并请求 http://127.0.0.1:8888/
控制台输出:
服务器启动了!
接收到一次请求!
我是中间件1!
我是中间件2!
*/
Koa 主要提供了什么呢?
- 中间件机制,这种模型极大的提高的扩展性,使的编写扩展非常简单。通过 context 在中间件之间传递每次请求时的应用状态,可以让中间件添加或修改属性,例如 bodyparser 会修改 ctx.request.body。
- 简化 API ,封装每次请求的原生 request 和 response 对象,ctx.req保存的是原生的 http 请求对象,而 ctx.request 是 koa 封装了的请求对象。在 ctx 绑定一些 ctx.request 和 ctx.response 的属性别名。ctx.request 和 ctx.response 上都增加了一些函数例如ctx.response.set(headerField, value) 提高易用性
- 对 web 的一些常用功能提供简单的支持,例如协商缓存,request.fresh 属性就是处理协商缓存的。
其它我感觉也没干啥。
koa 总共四个文件: application.js,context.js,request.js,response.js 加起来 2000 行代码左右。application.js 导出一个 Application 类,其它都是导出一个原型对象。我们从 koa 这个包导出的 Koa 类其实就是 Application 类,它抽象了服务器应用。context.js,request.js,response.js 这三个文件导出的都是原型对象,为了叙述方便,分别称导出的对象为 contextProtype, requestPrototype,responsePrototype。为什么叫原型对象。每因为当接受到一次请求时,我们在中间件访问的 ctx, ctx.request, ctx.response 都是通过 Object.create(contextProtype),Object.create(requestPrototype), Object.create(responsePrototype) 构造的,它们都是作为原型嘛。其它我就不展开说了,例如中间件是怎么实现的,也就是 koa compose 是怎么合并各个中间件成一个中间件函数以及会达到洋葱模型这种执行顺序的,有兴趣可以直接去看 koa-compose 的源码,代码 50 行不到,推荐看一下这位老哥的分析 koaComposeTest。
实际开发项目还需要使用社区的一些开源的中间件如 @koa/router 处理路由,koa-bodyparser 解析 json 请求体,@koa/multer 处理 multipart 的表单,@koa/cors 处理跨域,joi 处理参数校验,jsonwebtoken 来做 JWT 认证,以及自己封装一些中间件例如处理全局异常。然后还要用上很多开源工具比如 ORM 工具 sequelize,mongoose,测试工具 supertest, powerassert 等。所以一般用 koa 做开发前期肯定在配置各种中间件和工具库,每次都搞一遍太麻烦了,也可以看出 koa 顶多称之为底层框架,给你一个 vim,要想用的舒服得折腾一番。一般都会总结一套开发模板,比如我这个模板 koa RESTful boilerplate,或者就用它的上层框架比如 egg, thinkjs, daruk 等。现在 koa 以及一些相关的中间件貌似都是死马在维护,毕竟 koa 是 egg 的底层框架,他又是 egg 的核心开发者之一,底盘要稳。
nest 是一个真正意义上的 node 服务端框架。你在开发服务器需要的一切东西都给你准备好了,你只要照着它的风格使用对应的 module 就好了。
之前看到有人说用 nest 装饰器比 egg controller 写法上简化很多, 所以 egg 和 nest 的区别就是 JavaScript 和 TypeScript 语言之间的的区别。明显不是嘛,JavaSscript 也可以写装饰器啊。只不过我看现有的 JavaScript Server 框架貌似很少采用 IOC 一部分原因可能是因为 node 原生还不支持装饰器,要用装饰器可能就要上 webpack 或者类似的打包工具,无疑增加了复杂度。
如果设计一个 node server 框架,上 TypeScript 的话肯定得上 IOC。可以瞅一眼现有的一些 node Typescript server 框架哪个不是用装饰器来搞 IOC。用 Typescript 白嫖一个装饰器特性不用来搞 javaer 天天吹牛逼的 IOC 简直浪费。
要是有人问我 TypeScript 的看法,我肯定是强烈劝退。这玩意重度使用后你根本就不会有写 JavaScript 的欲望。所以还是别入坑了,这样你还能开开心心的用 JavaScript,感受 JavaScript 灵活性的酸爽,各种奇淫技巧和花里胡哨,不过你也要忍受经常因为手贱导致报了长长的错误和使用接口要靠猜的不安。以前用 js 写项目的时候我需要同时打开好几个框架和库的文档,用了 TypeScript 后直接看一遍快速上手就把文档给关了。而且依靠 TypeScript 的静态类型检查就是能在开发期间就避免很多低级错误,这是很重要的,毕竟谁也不愿意大晚上的被一个线上 bug 叫起来修 bug,结果是因为自己的低级错误导致的 bug。当然严格的单元测试也是非常必要的,良好的单元测试能保证你修改代码上线后不会出影响之前的功能,因为如果有影响一般测试都通过不了。有部分人可能会觉得 Javascript 这么灵活, TypeScript 在一些复杂情况下类型肯定不好定义。相信我,如果你有重度使用过 ts,你肯定会为 ts 类型的灵活性惊叹,ts 估计是绝大多数人碰到的类型最灵活的语言了。为什么TypeScript能在2019年这么火,那是因为它确实比较完美,10个人用了 ts,只有2个人没上瘾,绝对是因为那两个人没有重度使用。当一个东西它的粉丝只有增没有减的时候,大火是迟早的事。
我个人觉得,egg 如果不用 ts 重写,是很难赶上 nest 的。很多框架不是说你有类型定义就可以和 TypeScript 结合的很好了,不然 vue 为什么要用 ts 重写,因为 ts 不仅仅是语言的变化,还有风格的变化。mongoose 现在定义一个 model 还是用 js 时代的对象,明显用 class 来定义一个 model 才是具有类型系统的语言定义 model 的正常画风。
其实之前看到天猪说目前他们讨论的结果是目前用 TypeScript 重构 egg 没有任何好处。我觉得确实是这样的,用 TypeScript 重写 egg,那 egg 就不 egg 了。现有的 egg 确实就是 koa 这么一个优秀的新颖的架构基础和灵活的动态类型语言 javascript 结合的产物。可以动态扩展各种各样的工具,但是 TypeScript 做为一门静态类型语言,本就不适合去搞太动态化的东西,如果要使用 TypeScript 重写 egg,那必然写法上和现在的 egg 区别非常大,因为 TypeScript 作为一个拥有成熟的类型系统的语言肯定需要充分发挥类型系统的优势,上装饰器搞 IOC 基本上是必然的。egg 重写但风格不变也就提高了代码提示和静态类型检测,但是现在 egg 现在代码提示和工具支持的也还不错,收益不大。反而会带来一些问题,写起来会比用 JavaScript 繁琐很多,那时候你就会念想着装饰器的好处了,用了很多动态类型语言 JavaScript 的奇淫技巧重构起来我估计也兴趣不大。还不如自底向上整一个新框架叫 tegg, midway 可以观望一下。
为什么很多后端框架喜欢使用装饰器来搞 IOC,我个人觉得这样搞确实好处多多。采用集中声明式的书写依赖明显比手动编写 controller 逻辑代码更不容易产生错误。另外,由于装饰器是框架的一部分,所以框架本身管理项目的依赖就变得更容易和全面。再则装饰器设计的好的确实可以让代码变得非常精简,提高编码的愉悦性。
Koa 是个好框架,Typescript 是个好工具,Koa + TypeScript 不是个好组合,也就是有些人说 TypeScript + 框架就不是好东西了。其实就像我之前说过了 TypeScript 和 mongoose 也不是个好组合,TypeScript 和 typegoose 才是个好组合。Koa + TypeScript 要想用的舒服,必须得加上装饰器封装成一套框架。我感觉小嚼搞的 daruk 估计就是看中了目前 node server 界的一个空缺,没有比较火的结合了 TypeScript 和 koa 框架。其实你去 github 搜一下关键字 typescrpt koa 就知道了。
nest 有些人说是 node 版的 spring boot,我觉得不对,应该说 node 版的 angular。很多东西例如模块在 spring boot 里面应该没有,也许是我用 spring boot 用的比较少的原因。依赖注入这概念在 angular 里面就有,要论长得像明显是和 angular 一个模子刻出来的。我估计对 angular 不感冒的人对 nest 也不会感冒。还有一点可以说 nest 和 spring boot 风格完全不一样的理由是是代码的组织方式,大多数的框架 web MVC 是所有的 controller 放一个 cotrollers 文件夹,所有的 service 放 services 文件夹,但是 nest 更像是 angular 组件一样,所有和一个实体类相关的文件放一个文件夹,也就是说 UserModule.ts, User.ts,UserController.ts,UserService.ts,UserScheme.ts,UserValidate.ts 都是放一个 user 文件夹中。
目前我不满意的地方就是我看不到 nest 的创新点,没有自己的特色,给我的感觉就像是在写 angular,连官方文档都经常让你去看 angular 的文章。nest 底层依赖 express 和 fastify,这样学 nest 还得去学一下 express,我觉得这也是个问题,从长远考虑显然有所限制。
最后,nest 的中文文档翻译水平有待提高,egg 和 vue 能这么成功,一部分原因就是能有一个地道的中文文档。