从Nest到Nesk -- 模块化Node框架的实践
文: 达孚(沪江Web前端架构师)
本文原创,转至沪江技术
首先上一下项目地址(:>):
Nest:https://github.com/nestjs/nest
Nesk:https://github.com/kyoko-df/nesk
Nest初认识
Nest是一个深受angular激发的基于express的node框架,按照官网说明是一个旨在提供一个开箱即用的应用程序体系结构,允许轻松创建高度可测试,可扩展,松散耦合且易于维护的应用程序。
在设计层面虽然说是深受angular激发,但其实从后端开发角度来说类似于大家熟悉的Java Spring架构,使用了大量切面编程技巧,再通过装饰器的结合完全了关注上的分离。同时使用了Typescript(也支持Javascript)为主要开发语言,更保证了整个后端系统的健壮性。
强大的Nest架构
那首先为什么需要Nest框架,我们从去年开始大规模使用Node来替代原有的后端View层开发,给予了前端开发除了SPA以外的前后端分离方式。早期Node层的工作很简单-渲染页面代理接口,但在渐渐使用中大家会给Node层更多的寄托,尤其是一些内部项目中,你让后端还要将一些现有的SOA接口进行包装,对方往往是不愿意的。那么我们势必要在Node层承接更多的业务,包括不限于对数据的组合包装,对请求的权限校验,对请求数据的validate等等,早期我们的框架是最传统的MVC架构,但是我们翻阅业务代码,往往最后变成复杂且很难维护的Controller层代码(从权限校验到页面渲染一把撸到底:))。
那么我们现在看看Nest可以做什么?从一个最简单的官方例子开始看:
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
这里就启动了一个nest实例,先不看这个ValidationPipe,看ApplicationModule的内容:
@Module({
imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
configure(consumer: MiddlewaresConsumer): void {
consumer
.apply(LoggerMiddleware)
.with('ApplicationModule')
.forRoutes(CatsController);
}
}
@Module({
controllers: [CatsController],
components: [CatsService],
})
export class CatsModule {}
这里看到nest的第一层入口module,也就是模块化开发的根本,所有的controller,component等等都可以根据业务切分到某个模块,然后模块之间还可以嵌套,成为一个完整的体系,借用张nest官方的图:
在nest中的component概念其实一切可以注入的对象,对于依赖注入这个概念在此不做深入解释,可以理解为开发者不需要实例化类,框架会进行实例化且保存为单例供使用。
@Controller('cats')
@UseGuards(RolesGuard)
@UseInterceptors(LoggingInterceptor, TransformInterceptor)
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
@Get(':id')
findOne(
@Param('id', new ParseIntPipe())
id,
): Promise<Cat> {
return this.catsService.findOne(id);
}
}
Controller的代码非常精简,很多重复的工作都通过guards和interceptors解决,第一个装饰器Controller可以接受一个字符串参数,即为路由参数,也就是这个Controller会负责/cats路由下的所有处理。首先RolesGuard会进行权限校验,这个校验是自己实现的,大致结构如下:
@Guard()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(request, context: ExecutionContext): boolean {
const { parent, handler } = context;
const roles = this.reflector.get<string[]>('roles', handler);
if (!roles) {
return true;
}
// 自行实现
}
}
context可以获取controller的相关信息,再通过反射拿到handler上是否有定义roles的元信息,如果有就可以在逻辑里根据自己实现的auth方法或者用户类型来决定是否让用户访问相关handler。
interceptors即拦截器,它可以:
- 在方法执行之前/之后绑定额外的逻辑
- 转换从函数返回的结果
- 转换从函数抛出的异常
- 根据所选条件完全重写函数 (例如, 缓存目的)
本示例有两个拦截器一个用来记录函数执行的时间,另一个对结果进行一层包装,这两个需求都是开发中很常见的需求,而且拦截器会提供一个rxjs的观察者流来处理函数返回,支持异步函数,我们可以通过map()来mutate这个流的结果,可以通过do运算符来观察函数观察序列的执行状况,另外可以通过不返回流的方式,从而阻止函数的执行,LoggingInterceptor例子如下:
@Interceptor()
export class LoggingInterceptor implements NestInterceptor {
intercept(dataOrRequest, context: ExecutionContext, stream$: Observable<any>): Observable<any> {
console.log('Before...');
const now = Date.now();
return stream$.do(
() => console.log(`After... ${Date.now() - now}ms`),
);
}
}
回到最初的ValidationPipe,它是一个强大的校验工具,我们看到前面的controller代码中插入操作中有一个CreateCatDto,dto是一种数据传输对象,一个dto可以这样定义:
export class CreateCatDto {
@IsString() readonly name: string;
@IsInt() readonly age: number;
@IsString() readonly breed: string;
}
然后ValidationPipe会检查body是否符合这个dto,如果不符合就会就会执行你在pipe中设置的处理方案。具体是如何实现的可以再写一篇文章了,所以我推荐你看nest中文指南(顺便感谢翻译的同学们)
示例的完整代码可以看01-cats-app
也就是说业务团队中的熟练工或者架构师可以开发大量的模块,中间件,异常过滤器,管道,看守器,拦截器等等,而不太熟练的开发者只需要完成controller的开发,在controller上像搭积木般使用这些设施,即完成了对业务的完整搭建。
Nesk-一个落地方案的尝试
虽然我个人很喜欢Nest,但是我们公司已经有一套基于koa2的成熟框架Aconite,而Nest是基于express的,查看了下Nest的源码,对express有一定的依赖,但是koa2和express在都支持async语法后,差异属于可控范围下。另外nest接受一个express的实例,在nesk中我们只需要调整为koa实例,那么也可以是继承于koa的任何项目实例,我们的框架在2.0版本也是一个在koa上继承下来的node框架,基于此,我们只需要一个简单的adapter层就可以无缝接入Aconite到nesk中,这样减少了nesk和内部服务的捆绑,而将所有的公共内部服务整合保留在Aconite中。Nest对于我们来说只是一个更完美的开发范式,不承接任何公共模块。
所以我们需要的工作可以简单总结为:
- 支持Koa
- 适配Aconite
支持Koa我们在Nest的基础上做了一些小改动完成了Nesk来兼容Koa体系。我们只需要完成Nesk和Aconite中间的Adapter层,就可以完成Nesk的落地,最后启动处的代码变成:
import { NeskFactory } from '@neskjs/core';
import { NeskAconite } from '@hujiang/nesk-aconite';
import { ApplicationModule } from './app.module';
import { config } from './common/config';
import { middwares } from './common/middlware';
async function bootstrap() {
const server = new NeskAconite({
projectRoot: __dirname,
middlewares,
config
});
const app = await NeskFactory.create(ApplicationModule, server);
await app.listen(config.port);
}
最后Nest有很多@nest scope下的包,方便一些工具接入nest,如果他们与express没有关系,我们其实是可以直接使用的。但是包内部往往依赖@nest/common或者@nesk/core,这里可以使用module-alias,进行一个重指向(你可以尝试下graphql的例子):
"_moduleAliases": {
"@nestjs/common": "node_modules/@neskjs/common",
"@nestjs/core": "node_modules/@neskjs/core"
}
Nesk的地址Nesk,我们对Nesk做了基本流程测试目前覆盖了common和core,其它的在等待改进,欢迎一切愿意一起改动的开发者。
不足与期待
其实从一个更好的方面来说,我们应当允许nest接受不同的底层框架,即既可以使用express,也可以使用koa,通过一个adapter层抹平差异。不过这一块的改造成本会大一些。
另一方面nest有一些本身的不足,在依赖注入上,还是选择了ReflectiveInjector,而Angular已经开始使用了StaticInjector,理论上StaticInjector减少了对Map层级的查找,有更好的性能,这也是我们决定分叉出一个nesk的原因,可以做一些更大胆的内部代码修改。另外angular的依赖注入更强大,有例如useFactory和deps等方便测试替换的功能,是需要nest补充的.
最后所有的基于Koa的框架都会问到一个问题,能不能兼容eggjs(
从Nest到Nesk -- 模块化Node框架的实践的更多相关文章
- 【Nodejs】392- 基于阿里云的 Node.js 稳定性实践
前言 如果你看过 2018 Node.js 的用户报告,你会发现 Node.js 的使用有了进一步的增长,同时也出现了一些新的趋势. Node.js 的开发者更多的开始使用容器并积极的拥抱 Serve ...
- ABP vnext模块化架构的最佳实践的实现
在上一篇文章<手把手教你用Abp vnext构建API接口服务>中,我们用ABP vnext实现了WebAPI接口服务,但是并非ABP模块化架构的最佳实践.我本身也在学习ABP,我认为AB ...
- 好未来数据中台 Node.js BFF实践(一):基础篇
好未来数据中台 Node.js BFF实践系列文章列表: 基础篇 实战篇(TODO) 进阶篇(TODO) 好未来数据中台的Node.js中间层从7月份开始讨论可行性,截止到9月已经支持了4个平台,其中 ...
- IOC框架Ninject实践总结
原文地址:http://www.cnblogs.com/jeffwongishandsome/archive/2012/04/15/2450462.html IOC框架Ninject实践总结 一.控制 ...
- Slickflow.NET 开源工作流引擎基础介绍(六)--模块化架构设计和实践
前言:在集成Slickflow.NET 引擎组件过程中,引擎组件需要将用户,角色等资源数据读取进来,供引擎内部调用:而企业客户都是有自己的组织架构模型,在引入模块化架构设计后,引擎组件的集成性更加友好 ...
- .NET领域驱动设计—初尝(一:疑问、模式、原则、工具、过程、框架、实践)
.NET领域驱动设计—初尝(一:疑问.模式.原则.工具.过程.框架.实践) 2013-04-07 17:35:27 标签:.NET DDD 驱动设计 原创作品,允许转载,转载时请务必以超链接形式标明 ...
- Appium+python自动化(三十八) - Appium自动化测试框架综合实践 - 框架简介-助你冲击高薪,迎娶白富美(超详解)
简介 好久没有更新博客了,博友们是不是有点等不及了.不好意思啊,中秋节过后太忙了,这篇是好不容易抽点零碎时间写的.从这一篇开始小伙伴或者童鞋们,就跟随宏哥的脚步,一步步的从无到有,从0到1的搭建一个完 ...
- Appium+python自动化(三十九)-Appium自动化测试框架综合实践 - 代码实现(超详解)
简介 经过一段时间的准备,完善的差不多了,继续分享有关Appium自动化测试框架综合实践.想必小伙伴们有点等不及了吧! driver配置封装 kyb_caps.yaml 配置表 参考代码 platfo ...
- Appium+python自动化(四十)-Appium自动化测试框架综合实践 - 代码实现(超详解)
1.简介 今天我们紧接着上一篇继续分享Appium自动化测试框架综合实践 - 代码实现.由于时间的关系,宏哥这里用代码给小伙伴演示两个模块:注册和登录. 2.业务模块封装 因为现在各种APP的层出不群 ...
随机推荐
- 【BZOJ1001】狼抓兔子(网络流)
[BZOJ1001]狼抓兔子(网络流) 题面 Description 现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的, 而且现在的兔子还比较笨, ...
- CDQ分治 陌上花开(三维偏序)
CDQ分治或树套树可以切掉 CDQ框架: 先分 计算左边对右边的贡献 再和 所以这个题可以一维排序,二维CDQ,三维树状数组统计 CDQ代码 # include <stdio.h> # i ...
- Spring与Akka的集成
概述 近年来随着Spark的火热,Spark本身使用的开发语言Scala.用到的分布式内存文件系统Tachyon(现已更名为Alluxio)以及基于Actor并发编程模型的Akka都引起了大家的注意. ...
- 淘宝镜像 cnpm 不是内部命令
升级npm之后安装淘宝镜像,然后一直提示不是内部命令,网上查看文章 http://blog.csdn.net/fighting_2017/article/details/76979844,发现是路径问 ...
- BZOJ4825 单旋
分析:一道水题,去年考场发现了特点但是不会splay维护挂了,然后现在我写了个treap. 画一画图就可以解决这道题了,自己试一下. 代码如下: #include<bits/stdc++.h&g ...
- WordPress评论时一键填入昵称、邮箱和网址
现在很多博客都启用了多说,可是依然有很多博主坚守着wordpress或其主题自带的评论框,这样,每当我们访问这些博客时,发现精彩的内容或者 找到共鸣时.抑或只是想挑逗一下博主,准备在评论处爽爽的来一发 ...
- 《深入理解Bootstrap》读书笔记(一)
栅格系统 实现原理 通过定义容器大小,平分12份,再调整内外边距,最后结合媒体查询. 通过一系列包含内容的行和列来创建页面布局.下面列出了 Bootstrap 栅格系统是如何工作的: 1.行必须放置在 ...
- python文件读read()、readline()、readlines()对比
读取文件的三个方法:read().readline().readlines().均可接受一个变量用以限制每次读取的数据量,但通常不使用.本章目的是分析和总结三种读取方式的使用方法和特点. 一.read ...
- 用IDEA在Tomcat上部署项目
其实每次在需要运行的jsp页面右键=>run也是可以运行的,但是会出现下面这样 正常应该Run==>Edit Con-- 这时候将看到这个页面,千万不要在Defaults中招Tomcat配 ...
- C++通过ADO读写Excel文件
介绍 有时候我们需要从excel表格里导入.导出数据.其中一种方式就是通过ADO的方式.在这里,excel文件被当作数据库来处理,该方式不需要客户端安装Microsoft Excel,速度也够快. 连 ...